The Button
component allows users to commit a change or trigger an action via a single click or tap and is often found inside forms, dialogs, panels and/or pages.
The following section documents variants of the component that currently exist in Fabric and identifies variants that exist in other component libraries but don't currently exist in Fabric, documenting which component libraries have those variants.
The following are variants that don't affect the functionality of the Button
, but that have a visually distinct representation. A discussion needs to happen about how we support each of these variants and the effect of our theming story in that decision (separate components, via props, etc).
Action/Link button
Circular button
Compound button
Icon button
Primary button
Secondary/Default button
The following are variants that are functionally different from the base Button
variant. A discussion needs to happen about how much of this functionality belongs to base versus a different variant and about the need of maybe having a different spec for these variants if they are significantly different from the base Button
.
Menu button
Pressable
Split button
Toggle button
-
TODO - Need to discuss if functionality belongs to base button or separate variant.
-
The following are variants that exist because of the need of Buttons
to reside inside other components and, while functionally the same, the styling of these Buttons
is tightly coupled with the styling of these other components.
Command bar button
Message bar button
Animated button
- In Semantic UI
Block/Fluid button
- In Ant Design
- In Semantic UI
- In Shards React
- In Stardust UI
Button group/set
- In Ant Design
- In Atlaskit
- In Base Web
- In Carbon Design
- In Elemental UI
- In Material-UI
- In React Bootstrap
- In Semantic UI
- In Shards React
- In Stardust UI
Conditional button
- In Semantic UI
Floating action/Raised button
- In Material-UI
- In PrimeReact
Labelled button
- In Semantic UI
Outlined/Ghost button
- In Ant Design
- In Carbon Design
- In Chakra UI
- In Elemental UI
- In FastDNA
- In Material-UI
- In Shards React
Pill/Round button
- In Ant Design
- In Base Web
- In PrimeReact
- In Shards React
Tertiary button
- In Base Web
- In Carbon Design
- In React Bootstrap
The following section documents links to different UI libraries implementations of Buttons, while also providing a code sandbox with a side by side implementation of them for comparison.
-
FastDNA Button
The following section documents the properties that will become part of the new component, as well as the process for mitigating all changes when moving from Fabric and Stardust to Fluent UI.
TODO: Consult the prop wizard to derive consistently defined props.
Name | Type | Default value | Description |
---|
The Button
component should inherit the HTML props of the web button
so that props like onClick
and aria
have the same typings as the native web counterparts.
Name | Type | Default value | Description |
---|---|---|---|
as |
string |
Defines a component that should be used as the root element of the Button . |
|
className |
string |
Defines an additional classname to provide on the root of the Button . |
|
disabled |
boolean |
false |
Defines whether the Button is in an enabled or disabled state. |
href |
string |
Defines an href that, if provided, will make the Button render as an anchor. |
|
primary |
boolean |
false |
Defines whether the visual representation of the Button should be emphasized or not. |
TODO: Talk about the inheritance of native props, what should we do about them?
Pick
,Omit
, get all of them? What do we do about slots? Do we defect toany
?
Name | Type | Default value | Description |
---|---|---|---|
focus |
() => void |
Sets focus on the Button . |
Name | Description | Concern |
---|---|---|
allowDisabledFocus |
Defines whether disabled Buttons should be tabbable via keyboard navigation or not. |
Should we really redefine standard behavior here? |
checked |
Defines whether the Button is in a checked state. |
Does this belong to the base or to a variant ToggleButton ? |
circular |
Defines whether the Button should be rendered as a circle instead of as a rectangle. |
Should we still have this prop or should we generate a CircularButton variant via recomposition of styles? |
primary |
Defines whether the visual representation of the Button should be emphasized or not. |
Should we still have this prop or should we generate a PrimaryButton variant via recomposition of styles? |
https://developer.microsoft.com/en-us/fabric#/controls/web/button
Name | Type | Notes |
---|---|---|
dismissMenu |
() => void |
Should not be part of the base Button . Should be considered for MenuButton . Maybe just bring it from the Menu interface. |
focus |
() => void |
|
openMenu |
(shouldFocusOnContainer?: boolean, shouldFocusOnMount?: boolean) => void |
Should not be part of the base Button . Should be considered for MenuButton . Maybe just bring it from the Menu interface. |
Name | Type | Notes |
---|---|---|
allowDisabledFocus |
boolean |
Should we really redefine standard behavior here? |
ariaDescription |
string |
What purpose does this serve? If anything, belongs to CompoundButton and not to base. |
ariaHidden |
boolean |
Should use native aria-hidden instead. |
ariaLabel |
string |
Should use native aria-label instead. |
buttonType |
ButtonType |
Already deprecated. |
checked |
boolean |
Does this belong to the base or to a variant ToggleButton ? |
className |
string |
|
componentRef |
IRefObject<IButton> |
|
data |
any |
What purpose does this serve? Maybe remove? |
defaultRender |
any |
What purpose does this serve? Maybe remove? |
description |
string |
Already deprecated in favor of secondaryText . |
disabled |
boolean |
|
getClassNames |
(props) => IButtonClassNames |
Should be deprecated in favor of new composition approach. |
getSplitButtonClassNames |
(props) => IButtonClassNames |
Should not be part of the base Button . Should be considered for SplitButton . Should be deprecated in favor of new composition approach. |
href |
string |
|
iconProps |
IIconProps |
Should be replaced by slotProps . |
keytipProps |
IKeytipProps |
Should be removed until we add Keytips in Fluent UI. |
menuAs |
IComponentAs<IContextualMenuProps> |
Should be deprecated in favor of slot overrides. |
menuIconProps |
IIconProps |
Should not be part of the base Button and should be replaced by slotProps in MenuButton . |
menuProps |
IContextualMenuProps |
Should not be part of the base Button and should be replaced by slotProps in MenuButton . |
menuTriggerKeyCode |
KeyCodes | null |
Should not be part of the base Button . Should be considered for MenuButton . |
onAfterMenuDismiss |
() => void |
Should not be part of the base Button . Should be considered for MenuButton . Maybe rename to onDismiss ? |
onMenuClick |
(ev?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>, button?: IButtonProps) => void; |
Should not be part of the base Button . Should be considered for MenuButton . |
onRenderAriaDescription |
IRenderFunction<IButtonProps> |
Only keep if we are keeping ariaDescription . If keeping it, deprecate in favor of slot overrides. |
onRenderChildren |
IRenderFunction<IButtonProps> |
Should be removed or deprecated in favor of slot overrides. |
onRenderDescription |
IRenderFunction<IButtonProps> |
Should not be part of base Button . Could be considered for CompoundButton . In that case, deprecate in favor of slot overrides. |
onRenderIcon |
IRenderFunction<IButtonProps> |
Should be deprecated in favor of slot overrides. |
onRenderMenuIcon |
IRenderFunction<IButtonProps> |
Should not be part of the base Button . Should be considered for MenuButton . Should be deprecated in favor of slot overrides. |
onRenderMenu |
IRenderFunction<IContextualMenuProps> |
Should not be part of the base Button . Should be considered for MenuButton . Should be deprecated in favor of slot overrides. |
onRenderText |
IRenderFunction<IButtonProps> |
Should be deprecated in favor of slot overrides. |
persistMenu |
boolean |
Should this be handled as part of the menu slotProps instead of being a separate prop altogether? |
primary |
boolean |
|
primaryActionButtonProps |
IButtonProps |
Should not be part of the base Button . Should be replaced by a slot in SplitButton . |
primaryDisabled |
boolean |
Should not be part of the base Button . Should be considered for SplitButton . |
renderPersistedMenuHiddenOnMount |
boolean |
Already deprecated. |
rootProps |
React.ButtonHTMLAttributes<HTMLButtonElement> | React.AnchorHTMLAttributes<HTMLAnchorElement> |
Already deprecated. Should use slotProps instead. |
secondaryText |
string |
Should not be part of the base Button . Should be replaced by a slot in CompoundButton . |
split |
boolean |
Should be deprecated in favor of a SplitButton variant. |
splitButtonAriaLabel |
string |
Should not be part of the base Button . Should be considered for SplitButton . Maybe rename to secondaryActionAriaLabel ? |
splitButtonMenuProps |
IButtonProps |
Should not be part of the base Button . Should be replaced by a slot in SplitButton . |
styles |
IButtonStyles |
Should be deprecated in favor of recomposition. |
theme |
ITheme |
Should not show up in the public props contract. |
text |
string |
Should be replaced by a slot. |
toggle |
boolean |
Does this belong to the base or to a variant ToggleButton ? |
toggled |
boolean |
Already deprecated in favor of checked . |
uniqueId |
string | number |
This is used for keytip support in Fabric. Maybe remove it until we add Keytips in Fluent UI? |
Name | Type | Notes |
---|---|---|
accessibility |
Accessibility |
Why would a user need this as a prop? |
circular |
boolean |
|
disabled |
boolean |
|
fluid |
boolean |
Should this be a prop or should the library have a separate BlockButton variant? |
icon |
ShorthandValue<IconProps> |
Should be replaced by a slot. |
iconOnly |
boolean |
Should this be a prop or should the library have a separate IconButton variant? |
iconPosition |
'before' | 'after' |
Should be deprecated in favor of view recomposition. |
loader |
ShorthandValue<LoaderProps> |
What's the use case for this? |
loading |
boolean |
Should be deprecated in favor of recomposition. |
onClick |
ComponentEventHandler<ButtonProps> |
Should be replaced by the native event signature from which we extend. |
onFocus |
ComponentEventHandler<ButtonProps> |
Should be replaced by the native event signature from which we extend. |
primary |
boolean |
|
secondary |
boolean |
Does this change styling or is this just the default? |
size |
SizeValue |
Should this prop be provided or is this just a matter that could be solved via styling and recomposition? |
text |
boolean |
Should this be a prop or should the library have a separate GhostButton variant? If a prop, should it be named ghost instead? |
Name | Action to take/taken | Property transitioned? | Breaking change? | Codemod/Shim created? |
---|---|---|---|---|
dismissMenu |
TBD | ❌ | ❌ | ❌ |
focus |
TBD | ❌ | ❌ | ❌ |
openMenu |
TBD | ❌ | ❌ | ❌ |
Name | Action to take/taken | Property transitioned? | Breaking change? | Codemod/Shim created? |
---|---|---|---|---|
allowDisabledFocus |
TBD | ❌ | ❌ | ❌ |
ariaDescription |
TBD | ❌ | ❌ | ❌ |
ariaHidden |
TBD | ❌ | ❌ | ❌ |
ariaLabel |
TBD | ❌ | ❌ | ❌ |
buttonType |
Removing as it is already deprecated. | ☑ | No, because prop is already deprecated. | ❌ |
checked |
TBD | ❌ | ❌ | ❌ |
className |
TBD | ❌ | ❌ | ❌ |
componentRef |
TBD | ❌ | ❌ | ❌ |
data |
TBD | ❌ | ❌ | ❌ |
defaultRender |
TBD | ❌ | ❌ | ❌ |
description |
Removing as it is already deprecated. | ☑ | No, because prop is already deprecated. | ❌ |
disabled |
TBD | ❌ | ❌ | ❌ |
getClassNames |
TBD | ❌ | ❌ | ❌ |
getSplitButtonClassNames |
TBD | ❌ | ❌ | ❌ |
href |
TBD | ❌ | ❌ | ❌ |
iconProps |
TBD | ❌ | ❌ | ❌ |
keytipProps |
TBD | ❌ | ❌ | ❌ |
menuAs |
TBD | ❌ | ❌ | ❌ |
menuIconProps |
TBD | ❌ | ❌ | ❌ |
menuProps |
TBD | ❌ | ❌ | ❌ |
menuTriggerKeyCode |
TBD | ❌ | ❌ | ❌ |
onAfterMenuDismiss |
TBD | ❌ | ❌ | ❌ |
onMenuClick |
TBD | ❌ | ❌ | ❌ |
onRenderAriaDescription |
TBD | ❌ | ❌ | ❌ |
onRenderChildren |
TBD | ❌ | ❌ | ❌ |
onRenderDescription |
TBD | ❌ | ❌ | ❌ |
onRenderIcon |
TBD | ❌ | ❌ | ❌ |
onRenderMenuIcon |
TBD | ❌ | ❌ | ❌ |
onRenderMenu |
TBD | ❌ | ❌ | ❌ |
onRenderText |
TBD | ❌ | ❌ | ❌ |
persistMenu |
TBD | ❌ | ❌ | ❌ |
primary |
TBD | ❌ | ❌ | ❌ |
primaryActionButtonProps |
TBD | ❌ | ❌ | ❌ |
primaryDisabled |
TBD | ❌ | ❌ | ❌ |
renderPersistedMenuHiddenOnMount |
Removing as it is already deprecated. | ☑ | No, because prop is already deprecated. | ❌ |
rootProps |
Removing as it is already deprecated. | ☑ | No, because prop is already deprecated. | ❌ |
secondaryText |
TBD | ❌ | ❌ | ❌ |
split |
TBD | ❌ | ❌ | ❌ |
splitButtonAriaLabel |
TBD | ❌ | ❌ | ❌ |
splitButtonMenuProps |
TBD | ❌ | ❌ | ❌ |
styles |
TBD | ❌ | ❌ | ❌ |
theme |
TBD | ❌ | ❌ | ❌ |
text |
TBD | ❌ | ❌ | ❌ |
toggle |
TBD | ❌ | ❌ | ❌ |
toggled |
Removing as it is already deprecated. | ☑ | No, because prop is already deprecated. | ❌ |
uniqueId |
TBD | ❌ | ❌ | ❌ |
Name | Action to take/taken | Property transitioned? | Breaking change? | Codemod/Shim created? |
---|---|---|---|---|
accessibility |
TBD | ❌ | ❌ | ❌ |
circular |
TBD | ❌ | ❌ | ❌ |
disabled |
TBD | ❌ | ❌ | ❌ |
fluid |
TBD | ❌ | ❌ | ❌ |
icon |
TBD | ❌ | ❌ | ❌ |
iconOnly |
TBD | ❌ | ❌ | ❌ |
iconPosition |
TBD | ❌ | ❌ | ❌ |
loader |
TBD | ❌ | ❌ | ❌ |
loading |
TBD | ❌ | ❌ | ❌ |
onClick |
TBD | ❌ | ❌ | ❌ |
onFocus |
TBD | ❌ | ❌ | ❌ |
primary |
TBD | ❌ | ❌ | ❌ |
secondary |
TBD | ❌ | ❌ | ❌ |
size |
TBD | ❌ | ❌ | ❌ |
text |
TBD | ❌ | ❌ | ❌ |
The following section documents the DOM structure for the component from different component library examples and then suggests a recommended DOM taking into consideration common patterns between the libraries reviewed.
<button type="button" class="ant-btn">
<i aria-label="icon: download" class="anticon anticon-download">
<svg
viewBox="64 64 896 896"
focusable="false"
class=""
data-icon="download"
width="1em"
height="1em"
fill="currentColor"
aria-hidden="true"
>
<path
d="M505.7 661a8 8 0 0 0 12.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"
></path>
</svg>
</i>
<span>Download</span>
</button>
Children
ofButton
components are rendered inside aspan
.
<button type="button" class="css-shc4i4">
<span class="css-j8fq0c">
<span class="css-8xpfx5">
<i>3d_rotation</i>
</span>
<span class="css-mu6jxl">Default</span>
</span>
</button>
- Icons can go before and/or after the
children
via theiconBefore
andiconAfter
props. - Uneeded extra
span
wrapper inside ofbutton
tag. - Both
icons
andchildren
are wrapped in styledspans
.
<button
data-baseweb="button"
class="b3 ay b4 b5 b6 b7 b8 b9 ba bb bc b1 bd mh mi bg bh bi bj ah jp bk ex bl bm bn bo fi fk d6 fj bt ae ms mt mu"
>
<div class="al j6">
<svg data-baseweb="icon" viewBox="0 0 24 24" class="by bz c0 d3 md">
<title>Arrow Right</title>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M6 12C6 12.5523 6.44772 13 7 13H14.5858L12.2929 15.2929C11.9024 15.6834 11.9024 16.3166 12.2929 16.7071C12.6834 17.0976 13.3166 17.0976 13.7071 16.7071L17.7071 12.7071C17.8946 12.5196 18 12.2652 18 12C18 11.7348 17.8946 11.4804 17.7071 11.2929L13.7071 7.29289C13.3166 6.90237 12.6834 6.90237 12.2929 7.29289C11.9024 7.68342 11.9024 8.31658 12.2929 8.70711L14.5858 11H7C6.44772 11 6 11.4477 6 12Z"
></path>
</svg>
</div>
Start Enhancer
</button>
- The concept of icon does not exist here, instead you can add enhancers which are effectively a
React.Node
that are placed behind or after thechildren
under a styleddiv
.
<button class="bx--btn bx--btn--primary" type="button">
With icon
<svg
focusable="false"
preserveAspectRatio="xMidYMid meet"
style="will-change: transform;"
xmlns="http://www.w3.org/2000/svg"
class="bx--btn__icon"
width="16"
height="16"
viewBox="0 0 16 16"
aria-hidden="true"
>
<path d="M9 7L9 3 7 3 7 7 3 7 3 9 7 9 7 13 9 13 9 9 13 9 13 7z"></path>
</svg>
</button>
None.
<button type="button" class="css-z57ty6">
<svg viewBox="0 0 24 24" focusable="false" role="presentation" class="css-yxiis9">
<g fill="currentColor">
<path
d="M11.114,14.556a1.252,1.252,0,0,0,1.768,0L22.568,4.87a.5.5,0,0,0-.281-.849A1.966,1.966,0,0,0,22,4H2a1.966,1.966,0,0,0-.289.021.5.5,0,0,0-.281.849Z"
></path>
<path
d="M23.888,5.832a.182.182,0,0,0-.2.039l-6.2,6.2a.251.251,0,0,0,0,.354l5.043,5.043a.75.75,0,1,1-1.06,1.061l-5.043-5.043a.25.25,0,0,0-.354,0l-2.129,2.129a2.75,2.75,0,0,1-3.888,0L7.926,13.488a.251.251,0,0,0-.354,0L2.529,18.531a.75.75,0,0,1-1.06-1.061l5.043-5.043a.251.251,0,0,0,0-.354l-6.2-6.2a.18.18,0,0,0-.2-.039A.182.182,0,0,0,0,6V18a2,2,0,0,0,2,2H22a2,2,0,0,0,2-2V6A.181.181,0,0,0,23.888,5.832Z"
></path>
</g>
</svg>
Email
</button>
- Icons can go before and/or after the
children
via theleftIcon
andrightIcon
props.
<button class="Button Button--default" type="button">
<!-- react-text: 161 -->
Default
<!-- /react-text --><!-- react-text: 162 -->
Button
<!-- /react-text -->
</button>
- Has no built-in support for icons via props.
<button type="button" class="ms-Button ms-Button--default root-172" data-is-focusable="true">
<span class="ms-Button-flexContainer flexContainer-99" data-automationid="splitbuttonprimary">
<i
data-icon-name="Upload"
role="presentation"
aria-hidden="true"
class="ms-Icon root-38 css-96 ms-Button-icon icon-84"
>
</i>
<span class="ms-Button-textContainer textContainer-83">
<span class="ms-Button-label label-126 x-hidden-focus" id="id__361">Standard</span>
</span>
</span>
</button>
- Icons via props are only supported on the left of the
children
inside theButton
. - Text can be provided via a
text
prop and viachildren
.- If both are provided, only the one provided via the
text
prop is rendered. - If
children
is not astring
, then both are rendered. - Text provided via the
text
prop is rendered inside two nested styledspans
, one for the text container and one for the actual text.
- If both are provided, only the one provided via the
<button class="c012">
<span class="c018">Button</span>
</button>
- Has no built-in support for icons via props.
- Extra styled
span
wrapschildren
inside of theButton
.
<button class="RCK Hsu mix Vxj aZc GmH adn a_A gpV hNT iyn BG7 gn8 L4E kVc" type="button">
<div class="tBJ dyH iFc SMy yTZ pBj tg7 mWe">Medium Sized Button</div>
</button>
- Has no built-in support for icons via props.
- Extends to container's width by default.
- Text has to be provided via a
text
prop aschildren
are not rendered.
<button type="button" class="StyledButton-sc-323bzc-0 iQekQI">
<div class="StyledBox-sc-13pk1d4-0 jHQJnz">
<svg aria-label="Edit" viewBox="0 0 24 24" class="StyledIcon-ofa7kd-0 iOkQrb">
<path
fill="none"
stroke="#000"
stroke-width="2"
d="M14,4 L20,10 L14,4 Z M22.2942268,5.29422684 C22.6840146,5.68401459 22.6812861,6.3187139 22.2864907,6.71350932 L9,20 L2,22 L4,15 L17.2864907,1.71350932 C17.680551,1.319449 18.3127724,1.31277239 18.7057732,1.70577316 L22.2942268,5.29422684 Z M3,19 L5,21 M7,17 L15,9"
></path>
</svg>
<div class="StyledBox__StyledBoxGap-sc-13pk1d4-1 iChEkS"></div>
Edit
</div>
</button>
- Icons via props are only supported on the left of the
children
inside theButton
. - Text has to be provided via the
label
prop aschildren
are not rendered. - A styled
div
is added in-between the icon and the text to add a gap between them. - Uneeded extra
div
wrapper inside ofbutton
tag.
<button
class="MuiButtonBase-root MuiButton-root MuiButton-contained jss985 MuiButton-containedSecondary"
tabindex="0"
type="button"
>
<span class="MuiButton-label">
<span class="MuiButton-startIcon MuiButton-iconSizeMedium">
<svg class="MuiSvgIcon-root" focusable="false" viewBox="0 0 24 24" aria-hidden="true" role="presentation">
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"></path>
</svg>
</span>
Delete
</span>
<span class="MuiTouchRipple-root"> </span>
</button>
- Icons can go before and/or after the
children
via thestartIcon
andendIcon
props. - Both
children
and icons are wrapped inside of astyled
span. - An extra
span
is added inside of theButton
for styling the ripple effect in Material-UI.
<button class="p-button p-component p-button-text-icon-left">
<span class="pi pi-check p-c p-button-icon-left">::before</span>
<span class="p-button-text p-c">Click</span>
</button>
- Icons can go before and/or after the
children
via theiconPos
prop. - Icons are rendered via the
:before
andcontent
css props.
<button type="button" class="btn btn-secondary">Secondary</button>
- Has no built-in support for icons via props.
<button class="ui facebook button">
<i aria-hidden="true" class="facebook icon">::before</i>
Facebook
</button>
- Icons via props are only supported if
children
are not being rendered. - Icons are rendered via the
:before
andcontent
css props.
<button class="btn btn-secondary">Secondary</button>
- Has no built-in support for icons via props.
<button
class="ui-button jz lc oh mt je le lf lg lh cl cp cn dd bj rm bl rn cb gz as at au av ro rp rq rr rs ln ha hb hc hd he hf hg hh hi hj hk hl hm hn ho hp ot ou ov ow hu hv hw hx hy hz ia ib ic id ie if ig ih ii ij ik il im ox oy oz pa ir is it iu iv iw ix iy rt ru"
>
<span class="ui-icon ck cb gw rv ca" role="img" aria-hidden="true">
<svg class="cz ct cu da cw" viewBox="8 8 16 16" role="presentation" focusable="false">
<g class="ui-icon__outline cy">
<path
d="M23.6968,12.0403c-0.1836-0.0786-0.3975-0.04-0.542,0.0981l-2.5317,2.4165C20.2212,14.9382,20,15.4514,20,16 c0,0.5483,0.2212,1.0615,0.623,1.4448l2.5317,2.4167C23.2495,19.9521,23.374,20,23.5,20c0.0664,0,0.1333-0.0132,0.1968-0.0403 C23.8809,19.8809,24,19.7002,24,19.5v-7C24,12.2998,23.8809,12.1191,23.6968,12.0403z M23,18.3315l-1.6865-1.6099v-0.0002 C21.1113,16.5286,21,16.2725,21,16s0.1113-0.5286,0.3135-0.7217L23,13.6685V18.3315z"
></path>
<path
d="M17.5,11H9.8193c-0.7056,0-1.3232,0.5393-1.4692,1.2822C8.1177,13.4619,8,14.7129,8,16s0.1177,2.5381,0.3501,3.7173 C8.4961,20.4607,9.1138,21,9.8193,21H17.5c0.8271,0,1.5-0.6729,1.5-1.5v-7C19,11.6729,18.3271,11,17.5,11z M18,19.5 c0,0.2756-0.2241,0.5-0.5,0.5H9.8193c-0.2285,0-0.4341-0.2-0.4878-0.4756C9.1113,18.4082,9,17.2224,9,16 s0.1113-2.4082,0.3315-3.5249C9.3853,12.2,9.5908,12,9.8193,12H17.5c0.2759,0,0.5,0.2244,0.5,0.5V19.5z"
></path>
</g>
<g class="ui-icon__filled">
<path
d="M23.6968,12.0403c-0.1841-0.0786-0.3975-0.04-0.542,0.0981l-2.5317,2.4165C20.2212,14.9382,20,15.4514,20,16 c0,0.5483,0.2212,1.0615,0.623,1.4448l2.5317,2.4167C23.2495,19.9521,23.374,20,23.5,20c0.0664,0,0.1333-0.0132,0.1968-0.0403 C23.8809,19.8809,24,19.7002,24,19.5v-7C24,12.2998,23.8809,12.1191,23.6968,12.0403z"
></path>
<path
d="M17.5,11H9.8193c-0.7056,0-1.3232,0.5393-1.4692,1.2822C8.1177,13.4619,8,14.7129,8,16s0.1177,2.5381,0.3501,3.7173 C8.4961,20.4607,9.1138,21,9.8193,21H17.5c0.8271,0,1.5-0.6729,1.5-1.5v-7C19,11.6729,18.3271,11,17.5,11z"
></path>
</g>
</svg>
</span>
<span dir="auto" class="ui-box lp lq lr cg ls ch">A text button with an icon</span>
</button>
- Icons via props are only supported if
children
are not being rendered.- The icon is rendered inside of a styled
span
.
- The icon is rendered inside of a styled
- Text can be provided via both the
content
prop andchildren
.- Text provided via the
content
prop is rendered inside of a styledspan
.
- Text provided via the
After looking at all the component libraries above and taking into consideration common patterns the following DOM is recommended.
<button class="root" role="button" type="button">
<i class="startIcon"></i>
{children}
<i class="endIcon"></i>
</button>
<a class="root" role="button" type="link">
<i class="startIcon"></i>
{children}
<i class="endIcon"></i>
</a>
From the recommended DOM above we can indicate which slots are going to be required:
Name | Considerations |
---|---|
root |
|
startIcon |
|
endIcon |
- Do we provide both a
startIcon
andendIcon
as recommended above?- Alternatively we could provide just an
icon
slot that is always on the left and provide a "button with icon on the right" variant via view recomposition.
- Alternatively we could provide just an
- Previously we had a
text
prop. Our recommended DOM above is taking that out in favor of just havingchildren
.- Do we want to still have a
text
orcontent
prop/slot? - If we do, where do we place
children
in relation to it?
- Do we want to still have a
Aria spec: https://www.w3.org/TR/wai-aria-1.1/#button https://www.w3.org/TR/wai-aria-practices/#button
The following section describes the different states in which a Button
can be throughout the course of interaction with it.
An enabled Button
communicates interaction by having styling that invite the user to click/tap on it to trigger an action.
A disabled Button
is non-interactive, disallowing the user to click/tap on it to trigger an action.
Typically disabled browser elements do now allow focus. This makes the control difficult for a blind user to know about it, or why it's disabled, without scanning the entire page. Therefore it is recommended to allow focus on disabled components and to make them readonly. This means we use aria-disabled
attributes, and not disabled
attributes, for defining a disabled state. This may sometimes require special attention to ignoring input events in the case a browser element might do something. In the past we've introduced an allowDisabledFocus
prop for component users to control this behavior.
A hovered Button
changes styling to communicate that the user has placed a cursor above it.
A focused Button
changes styling to communicate that the user has placed keyboard focus on it. This styling is usually the same to the one in the hovered state.
A pressed Button
changes styling to communicate that the user has clicked/tapped on it.
- Checked state in
Toggle buttons
,Menu buttons
andSplit buttons
.
The following is a set of keys that interact with the Button
component:
Key | Description |
---|---|
Space |
Triggers the Button's action. |
Enter |
Triggers the Button's action. |
Test: Possible to use this to capture mouse, though Safari does not have compatibility: https://developer.mozilla.org/en-US/docs/Web/API/Element/setPointerCapture
mouseenter
: Should immediately change the styling of theButton
so that it appears to be hovered.mouseleave
: Should immediately remove the hovered styling of theButton
.mousedown
: Should immediately change the styling of theButton
so that it appears to be pressed.mouseup
:- If triggered while cursor is still inside of the
Button's
boundaries, then it should trigger theButton's
action and immediately remove the pressed styling of theButton
. - If triggered outside of the
Button's
boundaries, then it should immediately remove the pressed styling of theButton
without triggering theButton's
action.
- If triggered while cursor is still inside of the
The same behavior as above translated for touch events. This means that there is no equivalent for mouseenter
and mouseleave
, which makes it so that the hovered state cannot be accessed.
- Should render the native element using the
as
prop, defaulting to a nativebutton
element, or a nativea
element if thehref
prop has been set. - Should mix in the native props expected for the
button
ora
native elements depending on if thehref
prop has been set. - Should be keyboard tabbable and focusable.
The aria-label
, aria-labelledby
and aria-describedby
properties are surfaced to the component interface but are required to be set by the component user to meet accessibility requirements.
The Button
component uses react-texture
to provide a recomposable implementation that has no runtime performance penalties. The BaseButton
implementation can be used to provide new slots
and default props
without the application of additional styling:
const FooButton = BaseButton.compose({
tokens: {},
styles: {},
slots: {}
});
const onClickAlert = () => {
alert('Clicked');
};
render() {
<FooButton onClick={onClickAlert}>
Click me!
</FooButton>
}
1 per slot 1 per state, tagged on root
Tokens represent the general look and feel of the various visual slots. Tokens feed into the styling at the right times in the right slot.
Regarding naming conventions, use a camelCased name following this format:
{slot}{property}{state (or none for default)}
. For example:thumbSizeHovered
.Common property names:
size
,background
,color
,borderRadius
Common states:
hovered
,pressed
,focused
,checked
,checkedHovered
,disabled
Name | Considerations |
---|---|
background |
|
backgroundDisabled |
|
backgroundHovered |
|
backgroundPressed |
|
borderColor |
|
borderColorDisabled |
|
borderColorHovered |
|
borderColorPressed |
|
borderRadius |
|
borderStyle |
|
borderWidth |
|
boxShadow |
|
boxShadowDisabled |
|
boxShadowHovered |
|
boxShadowPressed |
|
color |
|
colorDisabled |
|
colorHovered |
|
colorPressed |
|
fontFamily |
|
fontSize |
|
fontWeight |
|
height |
|
lineHeight |
|
margin |
|
maxHeight |
|
maxWidth |
|
minHeight |
|
minWidth |
|
outlineWidth |
|
padding |
|
width |
|
startIconColor |
|
startIconColorDisabled |
|
startIconColorHovered |
|
startIconColorPressed |
|
startIconFontSize |
|
startIconFontWeight |
|
endIconColor |
|
endIconColorDisabled |
|
endIconColorHovered |
|
endIconColorPressed |
|
endIconFontSize |
|
endIconFontWeight |
Name | Considerations |
---|---|
backgroundPrimary |
|
backgroundPrimaryDisabled |
|
backgroundPrimaryHovered |
|
backgroundPrimaryPressed |
|
borderColorPrimary |
|
borderColorPrimaryDisabled |
|
borderColorPrimaryHovered |
|
borderColorPrimaryPressed |
|
colorPrimary |
|
colorPrimaryDisabled |
|
colorPrimaryHovered |
|
colorPrimaryPressed |
|
startIconColorPrimary |
|
startIconColorPrimaryDisabled |
|
startIconColorPrimaryHovered |
|
startIconColorPrimaryPressed |
|
endIconColorPrimary |
|
endIconColorPrimaryDisabled |
|
endIconColorPrimaryHovered |
|
endIconColorPrimaryPressed |
NOTE! Stardust does not follow this convention. Their Button
currently uses these tokens:
backgroundColor: string
backgroundColorActive: string
backgroundColorDisabled: string
backgroundColorFocus: string
backgroundColorHover: string
borderColor: string
borderColorDisabled: string
borderColorHover: string
borderRadius: string
boxShadow: string
circularBackgroundColor: string
circularBackgroundColorActive: string
circularBackgroundColorFocus: string
circularBackgroundColorHover: string
circularBorderColor: string
circularBorderColorFocus: string
circularBorderColorHover: string
circularBorderRadius: string
circularColor: string
circularColorActive: string
color: string
colorDisabled: string
colorFocus: string
colorHover: string
contentFontSize: string
contentFontWeight: Property.FontWeight
contentLineHeight: string
height: string
loaderBorderSize: string
loaderSize: string
loaderSvgAnimationHeight: string
loaderSvgHeight: string
loadingMinWidth: string
maxWidth: string
minWidth: string
padding: string
sizeSmallContentFontSize: string
sizeSmallContentLineHeight: string
sizeSmallHeight: string
sizeSmallLoaderBorderSize: string
sizeSmallLoaderSvgAnimationHeight: string
sizeSmallLoaderSvgHeight: string
sizeSmallMinWidth: string
sizeSmallPadding: string
textColor: string
textColorDisabled: string
textColorHover: string
textPrimaryColor: string
textPrimaryColorHover: string
- What do we do about high contrast? Do we provide additional tokens?
TODO: Example use cases
TODO: If this component represents a selected value, how will that be used in an HTML form? Is there a code example to illustrate?
TODO: Is it possible this component could be rendered in a focus zone? If so, should the focus model change in that case?