Button

Trigger actions with clear hierarchy, consistent states, and full token control across every use case.

Accessibility

Make buttons perceivable, operable, and predictable for every user. The component brings the element, the focus ring and the disabled semantics. The accessible name, the busy state and the target size are yours.

What you get for free

  • A real <button>, so Tab, Enter and Space all work without a single ARIA attribute.
  • A :focus-visible outline drawn from lineWidthFocus and colorPrimaryBorder, so it follows your theme instead of the browser default.
  • With disabled: the native attribute on the button form, which removes it from the tab order and announces it as unavailable.
  • With disabled and an href: an <a> with the href dropped, tabIndex={-1} and aria-disabled, so it is neither focusable nor navigable.
  • An Icon from the kit is aria-hidden by default, so a glyph beside a label is not announced twice. Give it an aria-label and it flips to role="img".

Labels and accessible name

  • Always provide a clear label. Prefer action plus outcome, keep it short.
  • Icon-only buttons must carry an accessible name via aria-label. There is no text for one to come from, and the glyph is hidden from the accessibility tree.
  • The same holds for a Button whose only content is an avatar: a face is decorative markup, so name the action (“Open your profile”), not the person.
  • Do not repeat the visible label in aria-label. If they differ, the visible text must be contained in the accessible name (WCAG 2.5.3, Label in Name), or voice control users cannot say what they see.
  • Avoid relying on color, icon, or placement to explain meaning. The label should be enough.
Choose the element through href, not through variant. variant="link" is a look: on its own it still renders a <button>, which is the right element for an action and the wrong one for a destination.
  • Use a plain Button for actions: submit, save, open a modal, trigger a request.
  • Add href when the intent is navigation. It renders an anchor and keeps its look, so the browser's own link affordances (open in new tab, copy address, the status bar) come back.
  • For a new tab, set target="_blank" with rel="noreferrer", and say so in the label or a nearby hint.
  • Do not attach click handlers to non interactive wrappers around a Button. Keep the Button as the interactive target.

Keyboard

  • Reachable with Tab, activated with Enter or Space. Both come from the element, and nothing here overrides them.
  • Keep focus order aligned with reading order. Moving a button visually with CSS without moving it in the DOM breaks that. iconPlacement="end" only swaps the glyph and the label, not the tab order of anything around them.
  • If a Button opens a modal, drawer, or popover, move focus into it and return focus to the trigger on close.
  • In a Space.Compact toolbar every button is its own tab stop. That is correct for a group of independent actions; if the group is really one control with several options, build a radio group or a Segmented instead.

Focus and visibility

  • Keep the focus ring. antd draws it on :focus-visible only, so it never appears on a mouse click and there is nothing to suppress.
  • The indicator needs at least 3:1 contrast against what is adjacent to it. It is drawn from colorPrimaryBorder, so re-check it if you re-seed the theme.
  • A button that becomes disabled while focused loses focus to the document body. Prefer keeping it enabled and validating on click, or move focus yourself.

States

  • Disabled. Use it only when the action is truly unavailable. A disabled button is not focusable, so it cannot explain why: put the reason next to it, in text.
  • Loading. A loading button ignores clicks but sets no ARIA state, so it still announces as enabled. Add aria-busy yourself, and announce the result in a live region rather than relying on the spinner.
  • Toggle. A button that maintains an on/off state exposes it with aria-pressed, not with a color change alone.
  • Menu trigger. Dropdown sets aria-expanded on the child it wraps. If you build your own, set it yourself.

Target size

Two different rules, and the sizes clear them differently. The three heights come from controlHeight: 24px for small, 32px for medium, 40px for large.
  • WCAG 2.2 SC 2.5.8, level AA: 24 x 24 CSS pixels. Every size clears it, with small exactly at the line, so give small icon-only buttons real spacing around them rather than packing them edge to edge.
  • WCAG 2.2 SC 2.5.5, level AAA: 44 x 44. No size reaches it on its own. On touch surfaces, pad the hit area beyond the painted box or reach for block.
  • An icon-only button is a square of the control height, so its target is exactly that number in both directions. A labeled button is wider but no taller.
  • Compact mode moves the whole ramp down. If you ship defaultCompact, re-measure.

Contrast and color

  • Check every pair you actually ship, not just the primary one. variant="text" and variant="link" put ink straight onto the page background, where a light palette shade fails fastest.
  • solid on a preset or palette name paints white on that ramp's shade 6, and antd's ramps do not put every hue at the same brightness. Warm hues are the ones to check first.
  • The neutral ramps are the clearest failure: white on their middle shade fails the 4.5:1 text contrast. Use color="default" for a gray button.
  • ghost keeps the button's own ink and drops the fill, so it is only readable against a surface you have checked it on. A ghost button on a panel of its own color disappears.
  • Re-check both modes after any token change. Hover and active colors move independently of the resting one.
  • Never let danger styling be the only signal that an action is destructive. Say it in the label.

Motion

  • The click ripple is set through wave on ConfigProvider or ThemeScope, not per button. Turn it off globally with { disabled: true }.
  • Gate any animation you add yourself behind motion-safe:, so it stands down for a reader who has asked for reduced motion.
  • A spinner is motion too. A delay keeps it off the screen entirely when the work is fast.