Theme
Sound
Jump to a component
Core · Actions
Button
Button runs an action with a text label: Play, Follow, Remind me, Done.
Live, from @anicanon/sakuga 2.0.0
Sakuga for iOS and Android is coming
iOS
Coming soon
Button for iOS is in the roughs. The tokens it will read are the ones on this page, the same on every platform.
Android
Coming soon
Button for Android is in the roughs. The tokens it will read are the ones on this page, the same on every platform.
- Use it for
- An action with a word on it: the one main action of a view, the action beside it, and the confirmation in a dialog that deletes something.
- Not for
- An action inside a sentence or a row of textTextLink
- An action that one glyph says better than a wordIconButton
- Narrowing what a feed showsChip
- Switching between views of the same thingSegmentedControl
- A choice whose options each need a picture or a line of explanationOptionTiles
- Signing in with Apple or GoogleProviderButton
Guidelines
- primary
- The one main action per view (Play, Follow creator, Done). Never two primaries side by side.
- secondary
- The supporting action beside the primary (Follow, More info).
- outline
- A standalone action that should not compete with the primary (Remind me).
- ghost
- Quiet actions in rows and headers (See all).
- destructive
- The one action that confirms a deletion or takes access away (Delete my account, Disconnect Claude), filled in vermilion (
danger). It lives in the dialog that asks first, and it is that dialog's main action, in place of a primary. The way into it is aTextLink variant="destructive", never a red button on the page. Removals you can simply undo (Remove a tag, Unfollow) stay ghost.
size="lg"for hero actions,mdby default,smfor desktop-only secondary actions. Every size exceptsmmeets the 44px target.shape="pill"on phones; the default rounded corner (radius-md) on desktop.- Labels say what happens, in sentence case. Add
aria-labelwhen the visible label needs its object ("Follow" → "Follow EBA"). - Pass
hrefto render a link that looks like a button. trailingputs a value after the label, behind a hairline:{ value, mark, label }.markis drawn before the value as given: size it at the button's icon size and hide it from a screen reader (aria-hidden), asSpendButtondoes;labelis read after the value, so "~100" is heard as "~100 fragments". The core knows no unit. An action that spends fragments is AniCanonKit'sSpendButton, which fillstrailingwith the fragment mark. Keep the label a plain verb ("Generate", "Start", "Retake"); never write the price into the label.- media and media-secondary: the same two roles drawn over video or key art (hero Play and More info, Skip intro, shorts CTA). They never change with the theme.
- Pressing sinks it to 97% on
motion-instant; it settles back onmotion-fast. - With sound on, a press plays the press sound (README · Sound).
- A disabled primary turns into a quiet fill (
button-disabled-bg), not a faded white. Say why it's disabled in a visible line near it, never only in a tooltip.
import { Button } from '@anicanon/sakuga/ui';
export default function ButtonExample() {
return (
<>
<Button variant="primary" size="lg" icon="play">
Play S1 · E1
</Button>
<Button variant="secondary" size="lg" aria-label="Follow EBA">
+ Follow
</Button>
<Button variant="outline">Remind me</Button>
<Button variant="ghost">See all</Button>
<Button variant="destructive">Delete my account</Button>
<Button
variant="primary"
shape="pill"
size="lg"
icon="play"
style={{ flex: 1 }}
>
Play E1
</Button>
<Button variant="secondary" shape="pill" size="lg">
Follow
</Button>
</>
);
}PropsButtonProps
variant'primary' | 'secondary' | 'outline' | 'ghost' | 'media' | 'media-secondary' | 'destructive'Default 'primary'media and media-secondary sit over video or artwork and never change with the theme. destructive: the one action that confirms a deletion or takes access away, in the dialog that asks for it.
size'sm' | 'md' | 'lg'Default 'md'shape'rounded' | 'pill'pill on phones; rounded (radius-md) on desktop.
iconIconNameiconEndIconNamefullWidthbooleantrailingButtonTrailingasElementTypeRenders as another element, such as a router's link.
childrenReactNodeclassNamestringAn IconName is any of the names Icon lists.
Types
interface ButtonTrailing { value: ReactNode; /** Drawn before the value, at buttonIconSize(size). It must hide itself from a screen reader (aria-hidden); the label speaks for it. */ mark?: ReactNode; /** Read after the value, e.g. "fragments", so "~100" is heard as "~100 fragments". */ label?: string; }
Each token reads a role, and the role lands on a value in each theme. Change the role and every component that reads it follows.
Colour
button-primary-bgButton primary: fill.action-primaryneutral-925#0b0a10action-primaryneutral-0#ffffffbutton-primary-fgButton primary: label and icon.text-inverseneutral-0#fffffftext-inverseneutral-925#0b0a10button-secondary-bgButton secondary: fill.action-secondaryshade-a12rgba(11,10,16,0.12)action-secondarywhite-a14rgba(255,255,255,0.14)button-secondary-fgButton secondary: label and icon.text-primaryneutral-925#0b0a10text-primaryneutral-0#ffffffbutton-outline-borderButton outline: edge.border-controlshade-a52rgba(11,10,16,0.52)border-controlwhite-a45rgba(255,255,255,0.45)button-outline-fgButton outline: label and icon.text-primaryneutral-925#0b0a10text-primaryneutral-0#ffffffbutton-ghost-fgButton ghost: label and icon.text-secondaryneutral-800#2e2d36text-secondaryneutral-200#dcdcdebutton-ghost-hover-bgButton ghost: hover fill.bg-fill-subtleshade-a06rgba(11,10,16,0.06)bg-fill-subtlewhite-a08rgba(255,255,255,0.08)button-disabled-bgButton primary or media while it can't be used yet: a quiet fill, never a faded white.bg-fill-subtleshade-a06rgba(11,10,16,0.06)bg-fill-subtlewhite-a08rgba(255,255,255,0.08)button-disabled-fgButton label while disabled.text-mutedneutral-600#5c5b67text-mutedneutral-400#a3a3a7button-media-bgButton media: fill over video or artwork.action-medianeutral-0#ffffffaction-medianeutral-0#ffffffbutton-media-fgButton media: label and icon.text-on-action-medianeutral-925#0b0a10text-on-action-medianeutral-925#0b0a10button-media-secondary-bgButton media-secondary: glass fill over video or artwork.action-media-secondaryglass-dark-a42rgba(28,28,34,0.42)action-media-secondaryglass-dark-a42rgba(28,28,34,0.42)button-media-secondary-fgButton media-secondary: label and icon.text-on-medianeutral-0#fffffftext-on-medianeutral-0#ffffffbutton-media-secondary-borderButton media-secondary: edge.border-glass-mediawhite-a16rgba(255,255,255,0.16)border-glass-mediawhite-a16rgba(255,255,255,0.16)button-destructive-bgButton destructive: the fill of the action that confirms a deletion or takes access away.dangervermilion-600#c8321edangervermilion-600#c8321ebutton-destructive-fgButton destructive: its label and icon.on-dangerneutral-0#ffffffon-dangerneutral-0#ffffffSpace and shape
button-radiusradius-md12px
button-pill-radiusradius-full9999px
button-height-smsize-control-sm36px
button-height-mdsize-control-md44px
button-height-lgsize-control-lg52px
button-gapgap-md8px
button-active-scalescale-press.97
button-padding-x-sminset-md12px
button-padding-x-mdinset-lg16px
button-padding-x-lginset-xl24px
button-outline-inset-ring-widthline-hairline1px
button-media-secondary-blurblur-glassblur(22px) saturate(1.8)
button-media-secondary-inset-ring-widthline-hairline1px
button-disabled-opacityopacity-disabled0.35
button-icon-size-lgicon-size-sm16px
button-icon-sizeicon-size-sm16px
button-cost-gapgap-xs4px
button-cost-margin-leftgap-2xs2px
button-cost-padding-leftinset-md12px
button-cost-border-left-widthline-hairline1px
button-cost-padding-left-sm-mdinset-sm8px
button-forced-edge-widthline-hairline1px
Type
button-font-familyfont-sansFunnel Sans
button-font-weightweight-bold700
button-font-size-smtext-label-bold-size14px
Aa
button-line-height-smtext-label-bold-line-height18px
button-font-size-mdtext-label-bold-size14px
Aa
button-line-height-mdtext-label-bold-line-height18px
button-font-size-lgits own value
16px
Aa
button-line-height-lgits own value
20px
button-secondary-font-weightweight-strong600
button-outline-font-weightweight-strong600
button-ghost-font-weightweight-strong600
button-media-secondary-font-weightweight-strong600
button-cost-font-weightweight-strong600
Motion
button-durationmotion-fast160ms
button-easingease-standardcubic-bezier(0.2, 0, 0, 1)
button-active-durationmotion-instant100ms
Checked automatically with axe-core 4.11.4 against WCAG 2.2 A and AA, on the live preview above, in light and dark, on 26 October 2026.
- Result
- No issues found, in light or in dark.
- By design
- A native button (a link with
href): Tab reaches it, Enter activates it, and Space does too on a button;disabledtakes a button out of the tab order. A screen reader hears its text (oraria-label), withtrailing's value and itslabelread after it ("~100 fragments"); icons and the mark are hidden. Withhrefanddisabledit is drawn as a disabled button, out of the tab order, since a link cannot be disabled. In forced colours it keeps an edge in ButtonText, and a disabled one turns GrayText. - Not tested
- The automated check doesn't test keyboard use, focus order or what a screen reader says. The lines above are the design system's own account of them.