Theming
One set of design tokens (named colors, sizes, and radii) covers every surface: popup, sidepanel, options, welcome, and the on-page UIs. To rebrand, swap one color scale; no component edits needed.
Swap one scale to rebrand
Section titled “Swap one scale to rebrand”The scales live in apps/extension/assets/tailwind.css (the @theme
block). The stock Tailwind palette is disabled (--color-*: initial), so
raw palette utilities (bg-blue-600, text-gray-500) don’t compile, and
lint bans them too. Component code references intent, never hue:
| scale | role |
|---|---|
neutral |
the only gray: surfaces, borders, text |
accent |
brand + every primary action; swap this scale to rebrand |
success |
paid/active states, confirmations |
warning |
past-due, cautions |
danger |
destructive actions, errors |
To rebrand, replace the eleven --color-accent-* oklch values in the
@theme block with your brand’s scale (Tailwind v4’s palette reference is
a good source of ready-made scales):
/* apps/extension/assets/tailwind.css: swap these for your brand */@theme { --color-accent-50: oklch(0.97 0.014 254.604); --color-accent-100: oklch(0.932 0.032 255.585); /* … 200–900 … */ --color-accent-950: oklch(0.282 0.091 267.935);}Every button, link, ring, and wall across every surface follows.
The pairing convention for tinted chips and banners:
{scale}-50 background / {scale}-800 text / {scale}-200 border in
light mode; {scale}-950 / {scale}-200 / {scale}-800 in dark (see
BroadcastBanner for the reference implementation).
Dark and light – always both
Section titled “Dark and light – always both”Dark mode is class strategy (@custom-variant dark), driven by the
settings store: auto follows the OS; light/dark override it. Style both
themes at authoring time, dark: variants throughout, and check every new
component in both themes before shipping.
Content-script shadow UIs get the theme class on their shadow wrapper,
never from the host page: the host page’s classes must not decide your
theme (mountShadowUi handles this).
Typography
Section titled “Typography”- InterVariable, bundled locally in
assets/fonts/; no CDN fonts, per the remote-code hygiene rule. Weights 100–900 in one variable file. - Headings are semibold (set in the base layer); body is regular.
- Numbers always get
tabular-nums(prices, credit counts, timers) so digits don’t jiggle. - Scale in practice:
text-baseheadings inside surfaces,text-smbody,text-xssecondary/meta. Popup surfaces are dense; avoid anything abovetext-lgoutside the welcome/options pages.
Radius – memorize this one
Section titled “Radius – memorize this one”| radius | used for |
|---|---|
rounded-lg |
controls: buttons, inputs, selects |
rounded-xl |
cards, panels, option rows |
rounded-full |
pills and avatars only |
Motion
Section titled “Motion”Fades and small translates only, 150–200 ms, with a hard cap at 300 ms
and no spring or bounce curves. Extension surfaces open and close
constantly; motion that draws attention twice a minute is noise. The one
sanctioned entrance: animate-in fade-in slide-in-from-bottom-4 on
transient chrome (status bar, toasts).
Spacing & layout
Section titled “Spacing & layout”4 px grid (the Tailwind default). Surfaces: p-4 sections, space-y-4
between blocks, gap-2/gap-3 inside rows. Popup min-width is min-w-90
(360 px); the options content column is max-w-xl.
Components
Section titled “Components”Primitives come from @extensionstart/ui: Button, Card, Input, Badge,
Skeleton, Dialog, Toast (CVA variants over Base UI). Never hand-roll a
<button> or badge in app code; extend via className, merged with
cn(). Focus styles are built into the primitives
(focus-visible:outline-2 outline-accent-600); custom interactive
elements must match.
Two practical notes:
- New Tailwind class sources outside the extension app need an
@sourceline inassets/tailwind.css(that’s how the workspace packages’ classes are picked up). - In shadow UIs, rem is converted to px at build (rem would resolve against the host page’s root font size); details in the UI-on-web-pages guide.
