Skip to content

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.

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:

scalerole
neutralthe only gray: surfaces, borders, text
accentbrand + every primary action; swap this scale to rebrand
successpaid/active states, confirmations
warningpast-due, cautions
dangerdestructive 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 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).

  • 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-base headings inside surfaces, text-sm body, text-xs secondary. Popup surfaces are dense; stay at or below text-lg outside the welcome page.
radiusused for
rounded-lgcontrols: buttons, inputs, selects
rounded-xlcards, panels, option rows
rounded-fullpills and avatars only

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).

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).

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 @source line in assets/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.