Theming
One set of design tokens covers every surface: popup, sidepanel, 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, 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 |
Replace the eleven --color-accent-* oklch values with your brand’s
scale (Tailwind v4’s palette reference has 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.
Tinted chips and banners pair {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, driven by the settings store: auto follows
the OS; light/dark override it. Write dark: variants as you author and
check every new component in both themes.
Content-script shadow UIs get the theme class on their shadow wrapper,
never from the host page (mountShadowUi handles this).
Typography
Section titled “Typography”- InterVariable, bundled locally in
assets/fonts/; no CDN fonts. - 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. Popup surfaces are dense; stay at or belowtext-lgoutside the welcome page.
Radius
Section titled “Radius”| 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, hard cap 300 ms, no spring
or bounce. 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 and layout
Section titled “Spacing and 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).
Components
Section titled “Components”Primitives come from @extensionstart/ui: Button, Card, Input, Badge,
Skeleton, Dialog, Toast. Never hand-roll a <button> or badge in app
code; extend via className, merged with cn(). Focus styles are built
into the primitives; custom interactive elements must match.
Two practical notes:
- New Tailwind class sources outside the extension app need an
@sourceline inassets/tailwind.css. - In shadow UIs, rem is converted to px at build (rem would resolve against the host page’s root font size); see UI on web pages.