Jump to a component
AniCanonKit · AniCanon™
SpendTiles
OptionTiles whose large cards carry fragments: an option's amount (what a pack or plan gives, the card's headline) and cost (what choosing it charges, under its description), each with the fragment mark. It takes every OptionTiles prop, with amount and cost as strings. The core OptionTiles knows nothing of fragments; its amount and cost are neutral, and this is the part that fills them.
Live, from @anicanon/sakuga 2.0.0
Sakuga for iOS and Android is coming
iOS
Coming soon
SpendTiles 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
SpendTiles 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
- A choice whose cards carry fragments: the Fragments packs and plans, and the first choice of a flow when each way costs something different.
- Not for
- A choice that costs nothingOptionTiles
- The action that spends themSpendButton
Guidelines
costis what an option charges, never what it gives; a pack's or plan's fragments are itsamount.- Put the money price in
description("$24.99 · never expires") and on the action ("Buy Bloom · $24.99"), never inamount.
import { useState } from 'react';
import { SpendTiles } from '@anicanon/sakuga/anicanonkit';
function Demo() {
const [value, setValue] = useState('bloom');
return (
<SpendTiles
size="lg"
columns={3}
label="Fragments packs"
value={value}
onChange={setValue}
options={[
{
value: 'spark',
label: 'Spark',
amount: '9,990',
description: '$9.99 · never expires',
},
{
value: 'bloom',
label: 'Bloom',
amount: '24,990',
description: '$24.99 · never expires',
},
{
value: 'cascade',
label: 'Cascade',
amount: '99,990',
description: '$99.99 · never expires',
},
]}
/>
);
}
export default function SpendTilesExample() {
return <Demo />;
}PropsSpendTilesProps
optionsrequired(Omit<TileOption, 'amount' | 'cost'> & { /** The fragments it gives, e.g. "24,990": the card's headline. */ amount?: string; /** What choosing it charges, e.g. "~10 a second". */ cost?: string; })[]From OptionTilesProps
valuerequiredstring | string[]classNamestringsize'lg'labelstringidstringmultiplebooleancolumnsnumbericonSizenumbernotestringhelp{ title: string; text: ReactNode; }helpOpenbooleanariaLabelstringonChange(value: string) => voidTypes
interface TileOption { value: string; label: string; icon?: IconName; description?: string; /** Large tiles: the thing's own art on a stage above its name (a Fragments pack or plan). */ image?: string; /** Large tiles: what it gives, set as the headline, e.g. "24,990"; as a ButtonTrailing it takes a mark before it and words read after it. Fragments: AniCanonKit's SpendTiles. */ amount?: string | ButtonTrailing; /** Large tiles: what choosing it charges, under the description, e.g. "~10 a second"; the same shape as amount. */ cost?: string | ButtonTrailing; /** Shown dimmed and cannot be chosen (a plan not open yet). */ disabled?: boolean; badge?: string; footnote?: string; }
It has no tokens of its own: it draws with OptionTiles’s, listed here.
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
option-tile-bgOptionTiles: idle tile.bg-fill-subtleshade-a06rgba(11,10,16,0.06)bg-fill-subtlewhite-a08rgba(255,255,255,0.08)option-tile-fgOptionTiles: idle icon and name.text-secondaryneutral-800#2e2d36text-secondaryneutral-200#dcdcdeoption-tile-hover-bgOptionTiles: hovered tile.bg-fillshade-a12rgba(11,10,16,0.12)bg-fillwhite-a14rgba(255,255,255,0.14)option-tile-selected-bgOptionTiles: chosen tile wash.accent-subtlecamellia-600-a10rgba(194,51,95,0.1)accent-subtlecamellia-400-a14rgba(244,119,159,0.14)option-tile-selected-borderOptionTiles: chosen tile edge.accentcamellia-600#c2335faccentcamellia-400#f4779foption-tile-selected-fgOptionTiles: chosen tile icon and name.text-primaryneutral-925#0b0a10text-primaryneutral-0#ffffffoption-tile-labelOptionTiles: the field name above the grid.text-mutedneutral-600#5c5b67text-mutedneutral-400#a3a3a7option-tile-noteOptionTiles: the line describing the chosen option.text-mutedneutral-600#5c5b67text-mutedneutral-400#a3a3a7option-card-bgOptionTiles large: card.bg-surfaceneutral-0#ffffffbg-surfaceneutral-900#111117option-card-borderOptionTiles large: edge.border-dividershade-a12rgba(11,10,16,0.12)border-dividerwhite-a12rgba(255,255,255,0.12)option-card-selected-bgOptionTiles large: chosen card.accent-subtlecamellia-600-a10rgba(194,51,95,0.1)accent-subtlecamellia-400-a14rgba(244,119,159,0.14)option-card-selected-borderOptionTiles large: chosen edge.accentcamellia-600#c2335faccentcamellia-400#f4779foption-card-iconOptionTiles large: icon.accentcamellia-600#c2335faccentcamellia-400#f4779foption-card-textOptionTiles large: description.text-secondaryneutral-800#2e2d36text-secondaryneutral-200#dcdcdeoption-card-footOptionTiles large: footnote.text-mutedneutral-600#5c5b67text-mutedneutral-400#a3a3a7Space and shape
option-field-gapgap-mdoption-field-label-gapgap-smoption-tiles-gapgap-smoption-tile-gapgap-smoption-tile-min-heightoption-tile-padding-yinset-smoption-tile-padding-xinset-xsoption-tile-radiusradius-mdoption-tile-active-scalescale-pressoption-tile-active-scale-lgscale-press-lgoption-tile-pressed-inset-ring-widthline-strongoption-tile-focus-outline-offsetfocus-ring-offsetoption-tiles-gap-lggap-xloption-tile-gap-lggap-mdoption-tile-padding-lginset-lgoption-tile-radius-lgradius-lgoption-tile-inset-ring-width-lgline-hairlineoption-tile-hover-inset-ring-width-lgline-hairlineoption-tile-pressed-inset-ring-width-lgline-strongoption-tile-foot-padding-top-lginset-xsoption-tile-top-icon-sizeicon-size-lgoption-tile-amount-icon-sizeicon-size-lgoption-tile-cost-icon-sizeicon-size-xsoption-tile-icon-sizeicon-size-mdoption-tile-cost-gapgap-xsoption-tiles-glide-radiusradius-mdoption-tiles-glide-inset-ring-widthline-strongoption-tiles-glide-radius-lgradius-lgoption-tiles-glide-inset-ring-width-lgline-strongoption-tile-disabled-opacity-lgopacity-disabledoption-tiles-tile-top-image-inset-lginset-lgoption-tile-art-heightoption-tile-art-margin-bottomgap-mdoption-tile-art-img-pressed-scale-lgscale-zoomoption-tile-amount-gapgap-mdoption-tiles-rows-tile-image-column-1-lgoption-tiles-rows-tile-image-column-gap-lggap-xloption-tiles-rows-tile-image-row-gap-lggap-2xsoption-tiles-rows-tile-art-sizeoption-tile-forced-edge-widthline-hairlineoption-tile-forced-selected-widthline-heavyType
option-field-label-fonttext-microoption-field-label-letter-spacingtracking-capsoption-tile-fonttext-small-strongoption-field-note-fonttext-smalloption-tile-title-font-lgtext-title-4option-tile-words-font-lgtext-bodyoption-tile-foot-font-lgtext-smalloption-tile-cost-fonttext-small-strongoption-tile-amount-fonttext-title-1option-tile-amount-letter-spacingoption-tiles-rows-tile-amount-font-sizetext-subtitle-sizeoption-tiles-rows-tile-amount-line-heighttext-subtitle-line-heightMotion
option-tile-durationmotion-fastoption-tile-easingease-standardoption-tile-active-durationmotion-instantoption-tile-art-img-durationmotion-baseoption-tile-art-img-easingease-standardChecked 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
- OptionTiles' own: with one choice the cards are a radio group, one Tab stop that the arrows move through. A screen reader hears each card's name, the amount or cost as "24,990 fragments", the description and the footnote; the mark and art are hidden.
- 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.