Theme
Sound
Jump to a component
AniCanonKit · Manga
LayerStrip
LayerStrip lists a page's layers by kind, in reading order, under the editor's canvas: panels, lettering, sound effects.
Live, from @anicanon/sakuga 2.0.0
Sakuga for iOS and Android is coming
iOS
Web only
LayerStrip is made for the web, and isn’t part of Sakuga for iOS yet.
Android
Web only
LayerStrip is made for the web, and isn’t part of Sakuga for Android yet.
- Use it for
- A page's layers under the editor's canvas: panels, lettering and sound effects, a track per kind in reading order, with the issue strip on top.
- Not for
- A cut's tracks in timeTimeline
- The issue's spreads on their ownIssueStrip
- Who or what a panel holdsMentionChip
Guidelines
- One track per kind; each item is its number (3.2) and, for words, how it is set and its first words. Panels show their picture once drawn.
- Choosing an item selects it on the page and the inspector edits that kind: a panel's direction and frame, a balloon's type and shape, a sound effect's style.
- Children sit on top: the issue's
IssueStripatsize18, so the page is always placed in its issue. - An empty kind says
emptyLabel("None yet"), never hides. onSelect(track, item)makes choosing work: each kind is a listbox, one Tab stop that Left and Right move through, and Enter, Space or a click choose.mutedmarks words drawn into the art rather than set as text: the picture dims, the words stay readable.
import { IssueStrip, LayerStrip } from '@anicanon/sakuga/anicanonkit';
const tracks = [
{
kind: 'panels',
label: 'Panels',
items: [
{
number: '3.1',
image: '/art/bad-memories-panel-sofa.webp',
selected: true,
},
{ number: '3.2' },
{ number: '3.3' },
{ number: '3.4' },
],
},
{
kind: 'lettering',
label: 'Lettering',
items: [
{ number: '3.1', label: 'Caption · I’m Bruce' },
{ number: '3.2', label: 'Caption · Every snowfall…' },
],
},
{ kind: 'sfx', label: 'Sound effects', items: [] },
];
const spreads = [
[{ image: '/art/bad-memories-the-winter-p00.webp', label: 'Front cover' }],
[
{
image: '/art/bad-memories-the-winter-bruce.webp',
label: 'Page 3',
selected: true,
},
{ image: '/art/bad-memories-the-winter-p04.webp', label: 'Page 4' },
],
[
{ image: '/art/bad-memories-the-winter-alarm.webp', label: 'Page 5' },
{
frames: [
{ x: 4, y: 4, w: 92, h: 30 },
{ x: 4, y: 36, w: 44, h: 28, state: 'drawing' },
{ x: 52, y: 36, w: 44, h: 28 },
{ x: 4, y: 66, w: 92, h: 30 },
],
label: 'Page 6, drawing',
},
],
[
{
frames: [
{ x: 4, y: 4, w: 56, h: 44 },
{ x: 62, y: 4, w: 34, h: 44 },
{ x: 4, y: 50, w: 92, h: 46 },
],
label: 'Page 7, laid out',
},
{
frames: [
{ x: 4, y: 4, w: 92, h: 22 },
{ x: 4, y: 28, w: 44, h: 34 },
{ x: 52, y: 28, w: 44, h: 34 },
{ x: 4, y: 64, w: 30, h: 32 },
{ x: 36, y: 64, w: 60, h: 32 },
],
label: 'Page 8, laid out',
},
],
[
{
frames: [
{ x: 4, y: 4, w: 92, h: 58 },
{ x: 4, y: 64, w: 92, h: 32 },
],
label: 'Page 9, laid out',
},
{ blank: true, label: 'Back cover, not made' },
],
];
export default function LayerStripExample() {
return (
<LayerStrip tracks={tracks}>
<IssueStrip size={18} label="The issue" spreads={spreads} />
</LayerStrip>
);
}PropsLayerStripProps
tracksrequiredLayerStripTrack[]emptyLabelstringchildrenReactNodeclassNamestringonSelect(track: number, item: number) => voidMakes each track a listbox, one Tab stop that Left and Right move through; Enter, Space or a click choose a layer.
Types
interface LayerStripTrack { kind: 'panels' | 'lettering' | 'sfx'; label: string; icon?: IconName; items: { number: string; label?: string; image?: string; selected?: boolean; /** Drawn into the art rather than set as text. */ muted?: boolean; }[]; }
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
layers-bgLayerStrip: the strip.bg-surfaceneutral-0#ffffffbg-surfaceneutral-900#111117layers-edgeLayerStrip: its edge and divider.border-hairlineshade-a08rgba(11,10,16,0.08)border-hairlinewhite-a06rgba(255,255,255,0.06)layers-headLayerStrip: a kind's name.text-secondaryneutral-800#2e2d36text-secondaryneutral-200#dcdcdelayers-itemLayerStrip: a layer.bg-fill-subtleshade-a06rgba(11,10,16,0.06)bg-fill-subtlewhite-a08rgba(255,255,255,0.08)layers-item-selectedLayerStrip: the selected layer.bg-surface-raisedneutral-100#ececf0bg-surface-raisedneutral-875#1a1a21layers-selectedLayerStrip: the selected layer's ring.accentcamellia-600#c2335faccentcamellia-400#f4779flayers-textLayerStrip: a layer's number.text-primaryneutral-925#0b0a10text-primaryneutral-0#fffffflayers-mutedLayerStrip: counts, lines and empty text.text-mutedneutral-600#5c5b67text-mutedneutral-400#a3a3a7layers-emptyLayerStrip: a panel not drawn yet.border-placeholdershade-a24rgba(11,10,16,0.24)border-placeholderwhite-a22rgba(255,255,255,0.22)Space and shape
layers-head-icon-sizeicon-size-sm16px
layers-gapgap-sm6px
layers-padding-yinset-sm8px
layers-padding-xinset-md12px
layers-radiusradius-md12px
layers-inset-ring-widthline-hairline1px
layers-top-padding-bottominset-sm8px
layers-top-margin-bottomgap-xs4px
layers-top-border-bottom-widthline-hairline1px
layers-track-column-1its own value
132px
layers-track-gapgap-md8px
layers-track-min-heightits own value
44px
layers-head-gapgap-md8px
layers-items-gapgap-sm6px
layers-items-paddinginset-2xs2px
layers-item-gapgap-md8px
layers-item-heightsize-control-md44px
layers-item-padding-rightinset-sm8px
layers-item-padding-leftinset-xs4px
layers-item-radiusradius-sm8px
layers-item-img-sizeits own value
32px
layers-item-img-radiusradius-xs4px
layers-item-i-max-widthits own value
180px
layers-item-selected-inset-ring-widthline-strong2px
layers-item-muted-opacityopacity-muted0.6
layers-empty-sizeits own value
32px
layers-empty-radiusradius-xs4px
layers-empty-inset-ring-widthline-strong2px
layers-forced-edge-widthline-hairline1px
layers-forced-selected-widthline-strong2px
Type
layers-head-fonttext-small-strong600 12px/16px "Funnel Sans", system-ui, -apple-system, "Segoe UI", sans-serif
layers-head-small-font-weightweight-medium500
layers-item-fonttext-small-strong600 12px/16px "Funnel Sans", system-ui, -apple-system, "Segoe UI", sans-serif
layers-item-i-font-weightweight-medium500
layers-none-fonttext-small500 12px/16px "Funnel Sans", system-ui, -apple-system, "Segoe UI", sans-serif
Motion
layers-item-durationmotion-fast160ms
layers-item-easingease-standardcubic-bezier(0.2, 0, 0, 1)
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
- Each track reads its name and count, then its layers: with
onSelect, a listbox named by the track and one Tab stop, where Left and Right move between layers, Enter, Space or a click choose one, and the chosen one reads "selected"; without it, a list whose selected item reads "current". Each layer reads its number and label, with "drawn into the art" whenmuted, pictures are hidden, and an empty track reads "None yet". - 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.