Buttons are interactive elements used to take an action, such as submitting a form or opening a dialog.


As far as functionality goes, you get a lot of stuff for free by using the native <button> element. This should be your first choice.

#Hover state

Indicates that the button is interactive when hovered with the mouse.

#Active state

Indicates that the button is pressed down.

#Focus state

Indicates that the focus in the document is currently on the button.

For the sake of aesthetics, it can be tempting to remove default browser focus styles with outline: 0;

Do not do this before adding your own custom focus styles!

#Disabled state

Indicates that the button is not interactive.

If necessary, used in combination with a Tooltip to provide a brief explanation on hover.

#Loading state

Indicates that the action taken via the button is asynchronous and will respond in a given amount of time.

Visually, it is common that a spinner is shown inside the button. Make sure that the button itself is not expanding in width or height while in this state.


Based on the W3 Success Criterion for Target Size, buttons should adapt to pointer inputs by expanding in size to a minimum of 44px. Optionally, buttons on mobile can also become block level full width elements.

#Best practices

  • Always use the native <button> element.
  • Clearly distinguish the hierarchy of buttons with different variants.
  • Write a button label that is succinct and short.
  • Provide an aria-label for buttons that don't contain text (e.g. icon buttons).


When building your component, keep in mind that buttons can often be designed to act like a link.

A common pattern in React is for the component to expose an as prop which changes the rendered element of the component, while keeping the visual the same.

<Button as="a">Open</Button>

#div buttons

Sometimes, you might be tempted to do this:

<div onClick={onClick}>Open</div>

By doing so, you lose a lot of the default behaviour baked into <button>. Avoid this at all cost and use the native element instead.

Although, there may be a case where you want to create a component with nested buttons:

function Tag({ onSelect, onRemove }) {
return (
<button onClick={onSelect}>
<button onClick={onRemove}>x</button>

This would not qualify as valid HTML, so you're gonna have to make the parent element a div.

In which case, you should be mindful to re-implement existing behaviour according to the WAI-ARIA spec for Button:

function DivButton({
'aria-label': ariaLabel,
}) {
function onKeyDown(e) {
if (disabled) {
if (e.key === 'Enter' || e.key === ' ') {
return (
{...!disabled && { onClick }}
{...ariaLabel && { ariaLabel }}
aria-disabled={disabled ? 'true' : 'false'}

Oof... that's a lot of stuff to cover. I hope you never have to do this! 😬