🍙 Onigiri

The Crunchyroll design system, rendered live from its own tokens and components. It cannot drift from what ships.

Color ramps

The brand orange, warm cream, and warm ink. Components never touch these directly: semantic tokens only. The big ruling: brand orange is a fill, not a text color.

orange
100#FFEFE5
200#FFD4BA
300#FFB88E
400#FF9C62
450#FF8036
500#FF640A
600#D25108
700#A43F06
800#762D04
900#481B02
cream
50#FFFDF9
100#F7F3E7
200#EFE9D8
300#D4CBAF
350#736D57
400#6A6450
ink
0#000000
100#141414
200#414141
300#777777
400#999999
500#BBBBBB
900#0A0A0A
status hues
moss-300#5BBE70
moss-700#3A7846
brick-300#CC5D5B
brick-700#C13937
slate-700#213944

Semantic roles

What components are allowed to reference, with both theme resolutions. Every pairing is contrast-checked on build: run npm run verify.

TokenLightDarkRole
color.bg.primary#F7F3E7#000000Page background.
color.bg.secondary#EFE9D8#141414Sunken wells, alt rows. Doubles as the hover wash on outline/ghost interactive surfaces.
color.bg.pressed#FFFDF9#414141Pressed wash on outline/ghost interactive surfaces.
color.bg.elevated#FFFDF9#141414Overlay surfaces that float above the page: menus, popovers. Pairs with shadow.overlay.
color.bg.positive#3A7846#3A7846Success toast surface. Theme-constant; carries text.on-status.
color.bg.negative#C13937#C13937Error toast surface. Theme-constant; carries text.on-status.
color.bg.info#213944#213944Informational banner surface, the recovered broadcast blue. Theme-constant; carries text.on-status.
color.bg.scrim#23252B80#23252B80The overlay wash behind modals and sheets — the old .Scrim (#23252B at 50%), token, not a component. Theme-constant.
color.bg.inverse#000000#F7F3E7Inverse blocks.
color.border.subtle#D4CBAF#414141Quiet outlines on interactive surfaces that carry their own text contrast. Decorative, not contracted.
color.border.strong#6A6450#777777Control boundaries that must read on their own (checkbox boxes, radio rings, switch tracks). Contracted at the 3:1 UI floor.
color.text.primary#000000#FFFDF9Reading text.
color.text.secondary#6A6450#BBBBBBCaptions, metadata.
color.text.disabled#736D57#777777Disabled — visible but muted. FIXED from the old 1.7:1.
color.text.positive#3A7846#5BBE70Good news in text: airtimes, new episodes, a strong password.
color.text.negative#C13937#CC5D5BErrors and destructive actions, in text.
color.text.warning#A43F06#D25108The Orange AA, not brand orange.
color.text.link#000000#FFFDF9Links rest in the reading color. Orange is earned on hover.
color.text.link-hover#A43F06#FF640ALink hover: the Orange AA on light, brand orange on dark.
color.text.link-active#762D04#FF8036FIXED from raw brand orange (2.8:1) to the deep AA step.
color.text.on-inverse#FFFDF9#000000Text on bg.inverse surfaces (inverse buttons, inverse blocks).
color.text.on-media#FFFDF9#FFFDF9Glyphs and labels over artwork and the scrim (thumbnail play icon, duration chip). Artwork is always dark, so this does not flip with the theme.
color.text.entry#000000#FFFDF9User-entered values in inputs. Robin's ruling: entered text reads white on dark (cream.50), one step brighter than reading text — your words stand out from the chrome.
color.text.on-status#FFFDF9#FFFDF9Text and glyphs on the status surfaces (bg.positive/negative/info). Theme-constant white.
color.accent.base#FF640A#FF640ABrand orange. A FILL color, not a text color: it fails 3:1 on cream (2.7), so on light it appears only as filled surfaces carrying ink text, or as large decorative marks.
color.accent.hover#FF8036#FF8036Accent fill on hover — lifts, does not darken. Same in both themes; ink text holds 8.4:1.
color.accent.pressed#D25108#D25108Accent fill pressed. Ink text holds 4.9:1.
color.accent.on-accent#000000#000000Text on brand-orange fills (base, hover, and pressed). Ink, not white — white on orange is 2.9:1. This is why Crunchyroll buttons have dark text.
color.accent.text#A43F06#FF640ABrand-colored text on quiet surfaces (brand outline/ghost buttons). The Orange AA on light, raw orange on dark. NOT the reference's hover value — orange.450 on the tint wash measured 2.2:1 and died at the gate.
color.accent.text-pressed#762D04#FF8036Brand-colored text, pressed.
color.accent.wash#FFEFE5#481B02Brand tint wash behind hovered/pressed brand outline/ghost buttons.

Type ramp

Twelve utilities welded from 68 tripled styles. Headlines carry the Atyp metrics in DM Sans Bold until the display face surfaces; bold UI text keeps the -3% brand tightening.

One piece at a timetype.display · 700 54px/64px
One piece at a timetype.title-1 · 700 34px/44px
One piece at a timetype.title-2 · 700 28px/36px
One piece at a timetype.title-3 · 700 24px/32px
One piece at a timetype.title-4 · 700 20px/26px
One piece at a timetype.body-lg · 400 18px/24px
One piece at a timetype.body · 400 16px/22px
One piece at a timetype.body-sm · 400 14px/20px
One piece at a timetype.label-lg · 700 16px/22px
One piece at a timetype.label · 700 14px/20px
One piece at a timetype.caption · 500 12px/18px
One piece at a timetype.micro · 500 10px/16px

Space

8pt scale with half-steps where components earned them. The scale is the law.

space-0
0px
space-2
2px
space-4
4px
space-6
6px
space-8
8px
space-12
12px
space-16
16px
space-20
20px
space-24
24px
space-28
28px
space-32
32px
space-36
36px
space-40
40px
space-44
44px
space-48
48px
space-56
56px
space-64
64px
space-80
80px
space-96
96px
space-128
128px

Radius & elevation

Pill actions, tight surfaces. Three shadow levels only: raised, overlay, modal. Dark separation leans on surface color, not shadow.

radius-none · 0px
radius-xs · 2px
radius-sm · 4px
radius-md · 8px
radius-lg · 12px
radius-xl · 16px
radius-2xl · 24px
radius-full · 999px
shadow-raised
shadow-overlay
shadow-modal