Developers
Everything in coach-admin's new UI reads from one set of CSS variables, generated from tokens/tokens.json. Components use semantic tokens such as bg-primary or text-muted-foreground, never a hex value.
Install
coach-admin moves to Tailwind CSS v4 and shadcn/ui. Add the generated stylesheet after Tailwind:
@import "tailwindcss";
@import "./tokens.css"; /* generated from tokens/tokens.json */
tokens.css holds:
- every primitive as a CSS variable (
--lime-300); - the semantic tokens for SuperCoach on
:root, and the Coach overrides under[data-brand="coach"]; - a Tailwind
@themethat resets Tailwind's own colors, radii, type scale, shadows and easing, and defines only the tokens. A class such asbg-red-500orshadow-2xsproduces no CSS, so values outside the system can't slip in.
Two products, one stylesheet
SuperCoach is the default theme on :root. Coach overrides only the tokens that differ, under [data-brand="coach"]. The buttons below read --primary and --secondary, so they change as soon as they sit inside a Coach element. Switch the preview's theme and direction to compare.
Using tokens
| Need | Tailwind | CSS |
|---|---|---|
| A semantic color | bg-card text-card-foreground | var(--card) |
| Spacing | p-4 gap-2 | --spacing(4) |
| Radius | rounded-lg | var(--radius-lg) |
| Shadow | shadow-md | var(--shadow-md) |
| Layer | z-(--z-modal) | var(--z-modal) |
| Motion | duration-(--duration-base) ease-standard | var(--duration-base) |
Every token page has a copy button for each of these. Never use a primitive (var(--lime-300)) in a component.
Right-to-left
Set dir="rtl" and lang="ar" on <html> for Arabic. Use logical utilities (ps-4, me-2, start-0) instead of left and right, so layouts mirror without extra CSS. Text inside lang="ar" switches to Lama Sans automatically. More on the RTL page.
<html lang="ar" dir="rtl" data-brand="coach">
The direction switch in this site's header only sets dir, because the site's own copy stays in English; previews add lang="ar" with their Arabic copy.
Root font size
Migrating from SCSS
Replace each SCSS color variable with its semantic token. The full table, with every variable found in coach-admin, is searchable below; it is also on the Color page.
127 of 127 variables
| SCSS variable | Today | Use this token | Nearest primitive (ΔE) | Action |
|---|---|---|---|---|
$white-text-color893 uses | #FFFFFF | --primary-foreground | white (0) | keep |
$primary-color464 uses | #2A46CB | --primary | brand-600 (0) | keep |
$black-text-color278 uses | #000000 | --foregroundDiffers from the nearest primitive: the semantic token wins. | black (0) | keep |
$dark-text-color224 uses | #1D2939 | --foregroundDiffers from the nearest primitive: the semantic token wins. | neutral-900 (3.7) | merge |
$grey-text-color165 uses | #4D4D4D | --foreground-secondary | neutral-700 (1.1) | merge |
$subtitle-text-color119 uses | #667085 | --muted-foreground | neutral-600 (5.7) | review |
$todo-text-muted119 uses | #667085 | No semantic token yet | neutral-600 (5.7) | review |
$color118 uses | #3B5998 | No semantic token yet | —social brand (CoreUI social-button loop, $color) | exclude |
$facebook118 uses | #3B5998 | No semantic token yet | —social brand (CoreUI social-button loop, $color) | exclude |
$invoice-ink-secondary90 uses | #5A6070 | --foreground-secondaryDiffers from the nearest primitive: the semantic token wins. | neutral-600 (2.9) | merge |
$secondary-color71 uses | #FF6D38 | No semantic token yet | orange-500 (0) | keep |
$light-surface-bg68 uses | #F5F5F7 | --mutedDiffers from the nearest primitive: the semantic token wins. | neutral-50 (0.8) | merge |
$border-color52 uses | #E4E4E4 | --border | neutral-200 (0.9) | merge |
$invoice-surface-bg42 uses | #F8F9FC | No semantic token yet | neutral-50 (0.4) | merge |
$error-color37 uses | #FF6868 | --destructiveDiffers from the nearest primitive: the semantic token wins. | red-500 (7.5) | review |
$todo-text-sub34 uses | #6B7280 | No semantic token yet | neutral-500 (5.4) | review |
$ai-darker-text33 uses | #1E1E1E | No semantic token yet | neutral-950 (2.2) | merge |
$danger-text-color32 uses | #B91C1C | --destructive-text | red-700 (0) | keep |
$navbar-color31 uses | #536C79 | No semantic token yet | neutral-600 (4) | merge |
$sidebar-nav-link-icon-color31 uses | #536C79 | No semantic token yet | neutral-600 (4) | merge |
$scpay-ink-strong30 uses | #344054 | No semantic token yet | neutral-700 (5.5) | review |
$media-surface-bg29 uses | #F0F0F0 | --muted | neutral-100 (0.3) | merge |
$success-color23 uses | #4DBD74 | No semantic token yet | green-500 (4.6) | merge |
$invoice-muted-text21 uses | #9199A8 | No semantic token yet | neutral-400 (4.4) | merge |
$invoice-warn-text21 uses | #92400E | --warning-text | amber-800 (0) | keep |
$warning-text-color21 uses | #92400E | --warning-text | amber-800 (0) | keep |
$todo-med-badge-fg21 uses | #92400E | No semantic token yet | amber-800 (0) | keep |
$danger-color21 uses | #EF4444 | --destructiveDiffers from the nearest primitive: the semantic token wins. | red-500 (0) | keep |
$todo-overdue-text21 uses | #EF4444 | No semantic token yet | red-500 (0) | keep |
$todo-high-bar21 uses | #EF4444 | No semantic token yet | red-500 (0) | keep |
$scpay-neutral-bg19 uses | #F2F4F7 | --muted | neutral-100 (1.1) | merge |
$warning-bg-color18 uses | #FFFBEB | No semantic token yet | amber-50 (0) | keep |
$warning-color18 uses | #D97706 | No semantic token yet | amber-600 (0) | keep |
$checkbox-unchecked18 uses | #EEEEEE | No semantic token yet | neutral-100 (0.7) | merge |
$input-placeholder-color16 uses | #C0C0C0 | No semantic token yet | neutral-300 (6.2) | review |
$danger-bg-color16 uses | #FEE2E2 | --destructive-subtleDiffers from the nearest primitive: the semantic token wins. | red-100 (0) | keep |
$todo-high-badge-bg16 uses | #FEE2E2 | No semantic token yet | red-100 (0) | keep |
$ai-light-gray-bg15 uses | #F8F8F8 | No semantic token yet | neutral-50 (0.2) | merge |
$sidebar-bg15 uses | #29363D | No semantic token yet | neutral-800 (2.2) | merge |
$navbar-active-color15 uses | #29363D | No semantic token yet | neutral-800 (2.2) | merge |
$aside-menu-color15 uses | #29363D | No semantic token yet | neutral-800 (2.2) | merge |
$input-affix-color13 uses | #91939E | No semantic token yet | neutral-400 (5.6) | review |
$upgrade-gradient13 uses | #982CB1 | No semantic token yet | violet-ai-500 (10) | review |
$upgrade-gradient-hover13 uses | #982CB1 | No semantic token yet | violet-ai-500 (10) | review |
$invoice-sar-bg13 uses | #F0FDF4 | --success-subtle | green-50 (0) | keep |
$success-surface-bg13 uses | #F0FDF4 | No semantic token yet | green-50 (0) | keep |
$invoice-usd-color12 uses | #1D4ED8 | --info | blue-700 (0) | keep |
$success-text-color11 uses | #15803D | --success-text | green-700 (0) | keep |
$invoice-net-color11 uses | #15803D | --success-text | green-700 (0) | keep |
$invoice-sar-color11 uses | #16A34A | --success | green-600 (0) | keep |
$todo-low-badge-fg11 uses | #16A34A | No semantic token yet | green-600 (0) | keep |
$dropdown-divider-bg10 uses | #F0F3F5 | No semantic token yet | neutral-100 (0.7) | merge |
$footer-bg10 uses | #F0F3F5 | No semantic token yet | neutral-100 (0.7) | merge |
$gray-10010 uses | #F0F3F5 | No semantic token yet | neutral-100 (0.7) | merge |
$disable-color10 uses | #B0B0B0 | No semantic token yet | neutral-400 (3.9) | merge |
$invoice-usd-bg9 uses | #EFF6FF | --info-subtleDiffers from the nearest primitive: the semantic token wins. | neutral-50 (1.5) | merge |
$border-strong-color9 uses | #C8CDD8 | --border-strong | neutral-300 (2.7) | merge |
$todo-low-bar9 uses | #22C55E | No semantic token yet | green-500 (0) | keep |
$ai-light-text9 uses | #5F5F5F | No semantic token yet | neutral-600 (1.4) | merge |
$overlay-body-text-color9 uses | #5F5F5F | No semantic token yet | neutral-600 (1.4) | merge |
$checkbox-hover9 uses | #CCCCCC | No semantic token yet | neutral-300 (2.5) | merge |
$invoice-section-line8 uses | #E2E5EC | No semantic token yet | neutral-200 (1) | merge |
$success-text-strong8 uses | #166534 | No semantic token yet | lime-800 (3.7) | merge |
$warning-border-color8 uses | #FDE68A | No semantic token yet | amber-200 (0) | keep |
$ai-secondary-text8 uses | #4A4A4A | No semantic token yet | neutral-700 (0) | keep |
$ai-icon-hover8 uses | #4A4A4A | No semantic token yet | neutral-700 (0) | keep |
$danger-border-color7 uses | #FCA5A5 | No semantic token yet | red-300 (0) | keep |
$invoice-egp-bg7 uses | #FFF7ED | No semantic token yet | orange-50 (0.3) | merge |
$invoice-sar-border7 uses | #86EFAC | No semantic token yet | green-300 (0) | keep |
$success-border-color7 uses | #86EFAC | No semantic token yet | green-300 (0) | keep |
$ai-dark-text7 uses | #777777 | No semantic token yet | neutral-500 (3.1) | merge |
$gray-2007 uses | #C2CFD6 | No semantic token yet | neutral-300 (2.9) | merge |
$sidebar-nav-title-color7 uses | #C2CFD6 | No semantic token yet | neutral-300 (2.9) | merge |
$dropdown-border-color7 uses | #C2CFD6 | No semantic token yet | neutral-300 (2.9) | merge |
$form-row-bg6 uses | #FBFBFD | No semantic token yet | neutral-50 (1) | merge |
$invoice-egp-color6 uses | #C2410C | No semantic token yet | orange-700 (3.4) | merge |
$paid-experience-muted-text-color6 uses | #6F7389 | No semantic token yet | neutral-500 (5.3) | review |
$scpay-primary-tint6 uses | #EEF1FC | --accent | brand-50 (1.1) | merge |
$todo-text-title6 uses | #1A1D23 | No semantic token yet | neutral-950 (2.1) | merge |
$todo-high-badge-fg6 uses | #DC2626 | No semantic token yet | red-600 (0) | keep |
$scpay-row-line6 uses | #EAECF0 | --borderDiffers from the nearest primitive: the semantic token wins. | neutral-100 (1.3) | merge |
$scpay-muted-dot6 uses | #98A2B3 | No semantic token yet | neutral-400 (2.9) | merge |
$scroll-thumb-color6 uses | #C7C6CC | No semantic token yet | neutral-300 (4.2) | merge |
$invoice-egp-border5 uses | #FDBA74 | No semantic token yet | orange-300 (0.6) | merge |
$invoice-usd-border5 uses | #BFDBFE | No semantic token yet | blue-200 (0) | keep |
$timeline-line-color5 uses | #F2F4FA | No semantic token yet | neutral-100 (1.3) | merge |
$medium-grey-text-color-hover4 uses | #232323 | No semantic token yet | neutral-900 (0.8) | merge |
$todo-low-badge-bg4 uses | #DCFCE7 | No semantic token yet | green-100 (0) | keep |
$proof-accent-color4 uses | #7ACA14 | No semantic token yet | lime-500 (2.3) | merge |
$medium-grey-text-color4 uses | #3E3E3E | --foreground-secondaryDiffers from the nearest primitive: the semantic token wins. | neutral-800 (4.3) | merge |
$verification-flag-gradient4 uses | #A324A3 | No semantic token yet | violet-ai-500 (12.1) | review |
$ai-accent-color4 uses | #7C5CBF | --ai | violet-ai-500 (0) | keep |
$verification-pulse-color3 uses | #43D426 | No semantic token yet | lime-500 (2.7) | merge |
$secondary-color-hover3 uses | #EDC2AE | No semantic token yet | red-200 (4.7) | merge |
$todo-med-bar3 uses | #F59E0B | No semantic token yet | amber-500 (0) | keep |
$todo-page-bg3 uses | #F5F6FA | No semantic token yet | neutral-50 (0.6) | merge |
$todo-med-badge-bg3 uses | #FEF3C7 | No semantic token yet | amber-100 (0) | keep |
$invoice-tabby-color3 uses | #6B36C9 | No semantic token yet | —3rd-party brand (Tabby) | exclude |
$invoice-tamara-color3 uses | #B23A1E | No semantic token yet | —3rd-party brand (Tamara) | exclude |
$paid-experience-check-bg3 uses | #EDF2FF | No semantic token yet | brand-50 (1) | merge |
$proof-quote-mark-color3 uses | #C3C8D4 | No semantic token yet | neutral-300 (4.1) | merge |
$scpay-primary-tint-border3 uses | #D6DCF5 | No semantic token yet | blue-200 (3.1) | merge |
$sticky-bg-color3 uses | #F5F7FA | No semantic token yet | neutral-50 (0.4) | merge |
$scroll-thumb-hover-color3 uses | #A7A4AD | No semantic token yet | neutral-400 (1.4) | merge |
$twitter2 uses | #00ACED | No semantic token yet | —social brand | exclude |
$nav-color2 uses | #060606 | --sidebar | neutral-950 (9.1) | review |
$ai-message-text2 uses | #363638 | No semantic token yet | neutral-800 (1.3) | merge |
$todo-grouped-border2 uses | #3B4FCB | No semantic token yet | brand-600 (3) | merge |
$linkedin2 uses | #4875B4 | No semantic token yet | —social brand | exclude |
$ai-accent-text-color2 uses | #5F3F9E | --ai-foreground | violet-ai-700 (0) | keep |
$overlay-muted-text-color2 uses | #64748B | No semantic token yet | navy-500 (4.9) | merge |
$ai-lighter-text2 uses | #727272 | No semantic token yet | neutral-500 (4.8) | merge |
$ai-icon-gray2 uses | #75787F | No semantic token yet | neutral-500 (3) | merge |
$ai-primary-color2 uses | #8A4FFF | No semantic token yet | violet-ai-500 (10.4) | review |
$checkbox-border-color2 uses | #8A93A6 | No semantic token yet | neutral-400 (6.4) | review |
$paid-experience-discount-bg2 uses | #91EC6B | No semantic token yet | lime-300 (5.5) | review |
$scpay-refund-text2 uses | #9A3412 | No semantic token yet | orange-800 (2.6) | merge |
$ai-footer-placeholder2 uses | #B7B7B7 | No semantic token yet | neutral-400 (6.1) | review |
$paid-experience-toggle-bg2 uses | #CFD5DF | No semantic token yet | neutral-300 (1.4) | merge |
$google-plus2 uses | #D34836 | No semantic token yet | red-600 (4.2) | merge |
$cell-border-color2 uses | #EBEDEF | No semantic token yet | neutral-100 (1.1) | merge |
$paid-experience-header-bg2 uses | #EEF2FF | --accent | brand-50 (0.9) | merge |
$invoice-tabby-bg2 uses | #F2EAFE | No semantic token yet | —3rd-party brand (Tabby) | exclude |
$paid-experience-panel-bg2 uses | #F7F7FC | No semantic token yet | neutral-50 (0.5) | merge |
$row-hover-bg2 uses | #FAFAFC | No semantic token yet | neutral-50 (0.7) | merge |
$invoice-tamara-bg2 uses | #FFF0E6 | No semantic token yet | —3rd-party brand (Tamara) | exclude |
$invoice-due-row-bg2 uses | #FFFDF4 | --warning-subtleDiffers from the nearest primitive: the semantic token wins. | white (1.4) | merge |
Spacing, radius and font sizes snap to the nearest step; see Spacing and Typography.
Regenerating tokens
pnpm tokens
pnpm dev watches tokens/tokens.json, so a changed value shows up on every page without a restart.