Buttons are interactive elements used to take an action, such as submitting a form or opening a dialog.
#Functionality
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.
#Responsive
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).
#Implementation
#Link 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}>Apple<button onClick={onRemove}>x</button></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({children,'aria-label': ariaLabel,disabled,onClick,}) {function onKeyDown(e) {if (disabled) {return;}if (e.key === 'Enter' || e.key === ' ') {onClick();}}return (<divtabIndex={0}role="button"onKeyDown={onKeyDown}{...!disabled && { onClick }}{...ariaLabel && { ariaLabel }}aria-disabled={disabled ? 'true' : 'false'}>{children}</div>);}
Oof... that's a lot of stuff to cover. I hope you never have to do this! 😬
#Accessibility
The accessibility requirements for this component are defined by the WAI-ARIA Practices for Button: