Jump to a component
Core · Actions
OptionTiles
OptionTiles is the Studio's picker: a grid of tiles, each an AniCanon™ icon over a short name, for shot type, angle, depth, movement, mood, lighting, weather, effects and frame format.
Live, from @anicanon/sakuga 2.0.0
Sakuga for iOS and Android is coming
iOS
Coming soon
OptionTiles 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
OptionTiles 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 that is clearer seen than read: Studio's direction pickers (shot, angle, lighting) as tiles, and with
size="lg"the first choice of a flow, a pack or a plan as cards. - Not for
- A short list of plain wordsSelect
- Two to four views of the same thingSegmentedControl
- One setting on or offSwitch
- One thing among many, with pictures and searchPicker
Guidelines
- Every option keeps its own drawn icon (
shot-closeup,lighting-neon,movement-dolly-in…). Auto usesauto. - One choice by default;
multiplefor effects. Tapping the chosen tile again clears it back to Auto. - With one choice, the selection ring slides to the new tile on
motion-base; a tile sinks on press. - With sound on and one choice, a new tile plays the choose sound (README · Sound).
- Put a
helpon every field: a bold first line saying what it controls, then examples. It shows through FieldHelp. - Pass
notewith the chosen option's one-line description, so the creator learns what it does. - 4 columns for long lists, 3 for short ones.
Large: choice cards
size="lg" turns each option into a card with icon, label, description, footnote and an optional badge. An option that sets a price passes cost ("~10 a second"), shown under the description. amount and cost are neutral: a string, or { value, mark, label } with a mark before the value and words read after it. The core knows no unit: AniCanon™'s fragments are AniCanonKit's SpendTiles, which takes the same options with amount and cost as strings and draws the fragment mark. Use it for the first choice of a flow (how to make something), three to four cards, with the chosen option's form directly below on the same screen.
An option with disabled (a plan that is not open yet, with a "Coming soon" badge) dims and cannot be chosen.
Fragments packs and plans use SpendTiles and pass image (the pack's or plan's own art: Spark, Bloom and Cascade grow from one flame to an orbit; Creator and Studio have their own) and amount (the fragments it gives, "24,990"), which is the card's headline with the mark. cost stays what an option charges, never what it gives. The art stands on its own stage above the name, and the chosen card's art comes forward a little. With columns={1} (a phone's sheet) the art sits beside the words. Put the price in description ("$24.99 · never expires") and on the action ("Buy Bloom · $24.99").
import { useState } from 'react';
import { OptionTiles } from '@anicanon/sakuga/ui';
const options = [
{ value: 'Auto', label: 'Auto', icon: 'auto' },
{ value: 'Extreme', label: 'Extreme', icon: 'shot-extreme' },
{ value: 'Close-up', label: 'Close-up', icon: 'shot-closeup' },
{ value: 'Med close', label: 'Med close', icon: 'shot-medium-closeup' },
{ value: 'Medium', label: 'Medium', icon: 'shot-medium' },
{ value: 'Full', label: 'Full', icon: 'shot-full' },
{ value: 'Wide', label: 'Wide', icon: 'shot-wide' },
{ value: 'Ext wide', label: 'Ext wide', icon: 'shot-extreme-wide' },
];
function Demo() {
const [value, setValue] = useState('Close-up');
return (
<OptionTiles
id="d-shot"
label="Shot type"
value={value}
onChange={setValue}
help={{
title: 'Defines how much of the subject and environment is visible.',
text: 'Examples: close-up for emotion, medium shot for dialogue, wide shot for location context.',
}}
note="Fills the frame with the face: for emotion."
options={options}
/>
);
}
export default function OptionTilesExample() {
return <Demo />;
}PropsOptionTilesProps
optionsrequiredTileOption[]valuerequiredstring | string[]labelstringidstringmultiplebooleansize'lg'columnsnumbericonSizenumbernotestringhelp{ title: string; text: ReactNode; }helpOpenbooleanariaLabelstringonChange(value: string) => voidclassNamestringTypes
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; }
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
- With one choice the tiles are a radio group: one Tab stop, on the chosen tile (the first when none is), so a Dialog opens on the chosen tile; the arrows move to a tile and choose it, Home and End go to the first and last, and an option's
disabledis skipped. Withmultipleeach tile is a toggle button: Tab focuses each one, and Enter or Space picks it. A screen reader hears the group, named by the words oflabel(orariaLabel), then each tile with its name, as a radio ("checked" when chosen) or withmultiplea toggle button ("pressed"), large tiles adding the badge, the amount or cost with its label ("24,990 fragments"), the description and the footnote, with icons and art 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.