Colors

7 semantic palettes + 1 runtime brand token. Primary → slate, Secondary → violet, Info → skyblue, Success → green, Warning → orange, Error → red, Neutral → gray.

Brand — Company Color (runtime)
25#f6f4fb
50#f1edf8
100#e2daf1
200#c5b5e2
300#a086d0
400#7a56bd
500#5b3b97
600#472e75
Info — Skyblue
25#F5FBFF
50#F0F9FF
100#E0F2FE
200#B9E6FE
300#7CD4FD
400#36BFFA
500#0BA5EC
600#0086C9
Success — Green
25#F6FEF9
50#ECFDF3
100#D1FADF
200#A6F4C5
300#6CE9A6
400#32D583
500#12B76A
600#039855
Warning — Orange
25#FFFCF5
50#FFFAEB
100#FEF0C7
200#FEDF89
300#FEC84B
400#FDB022
500#F79009
600#DC6803
Error — Red
25#FFFBFA
50#FEF3F2
100#FEE4E2
200#FECDCA
300#FDA29B
400#F97066
500#F04438
600#D92D20
Neutral
50#f5f5f7
100#eff2f5
200#eef0f4
300#d9dde7
400#637281
500#5C6C7C
600#475467

Shadows

6 levels — all use rgba(16, 24, 40, …) as base. Token: --shadow-{xs|sm|md|lg|xl|2xl}.

shadow-xs
0px 1px 2px
rgba(…, 0.05)
shadow-sm
0px 1px 3px
+ 1px 2px
shadow-md
0px 4px 8px
+ 2px 4px
shadow-lg
0px 12px 16px
+ 4px 6px
shadow-xl
0px 20px 24px
+ 8px 8px
shadow-2xl
0px 24px 48px
rgba(…, 0.18)

Frosted Glass

Translucent blurred surface that content sits on — cards, panels and composers floating over busy content (used throughout the AskVee modal). Apply the .frosted-glass class, or copy the CSS below. Tokens: --frosted-glass-fill, --frosted-glass-filter, --frosted-glass-border, --frosted-glass-shadow.

.frosted-glass
saturate(1.3) blur(16px) · fill rgba(255,255,255,.40)
.frosted-glass {
  background: rgba(255, 255, 255, 0.4);
  -webkit-backdrop-filter: saturate(1.3) blur(16px);
  backdrop-filter: saturate(1.3) blur(16px);
  border: 1px solid rgba(255, 255, 255, 0.75);
  box-shadow: 0 8px 32px rgba(16, 24, 40, .10),
              0 2px 8px rgba(16, 24, 40, .06);
}

Blurry Background

The AskVee modal backdrop — a soft coral / magenta / violet aurora glowing from the top, fading into a near-white ground. Apply the .blurry-background class, or copy the CSS below. Token: --blurry-background.

.blurry-background {
  background:
    radial-gradient(440px 320px at 6% -12%, rgba(255,136,93,.30), transparent 70%),
    radial-gradient(440px 320px at 94% -10%, rgba(208,65,93,.26), transparent 70%),
    radial-gradient(380px 300px at 50% -16%, rgba(106,75,219,.26), transparent 72%),
    #faf7fd;
}

Corner Radii

Token: --radius-{step}. 9 steps from none to pill.

none0px
2xs2px
xs4px
sm6px
DEFAULT8px
md10px
lg12px
xl16px
pill50px

Border Widths

5-step scale matching actual codebase usage frequency. --border-width-thin (1px) is the default; --border-width-md (2px) emphasises a focal element. --border-width-hairline (0.5px) is reserved for sub-pixel separators that need to stay visually lighter than 1px on high-DPI screens.

hairline0.5px
thin1px
md2px
thick3px
heavy5px
$border-width-hairline
Sub-pixel separators in dense lists, swiper navigation outlines, hairline dividers between table rows
$border-width-thin
Default — input borders, card outlines, divider lines, neutral container edges
$border-width-md
Emphasised borders — focus rings, selected card highlights, branded avatar rings, active tab underline
$border-width-thick
Heavy dividers — section separators in dense dashboards, prominent footer top-border
$border-width-heavy
Decorative rings — profile-header avatar stroke, large podium photo border, badge outline

Opacity

6-step opacity scale for visual hierarchy and interaction states. Apply via the opacity property on an existing element. For colour-with-alpha tints (e.g. tint a backdrop), prefer rgba(var(--color-rgb), α) instead.

Write opacity: 0 and opacity: 1 as literals — they're not tokens. They represent "hidden" and "fully visible" reset states.

faint0.3
disabled0.4
muted0.6
subtle0.7
soft0.8
strong0.9
$opacity-faint
Heavy dimming — long-deprecated content, context-hidden placeholder text, hard-to-reach states
$opacity-disabled
Default disabled state — disabled buttons, locked form fields, inactive list items
$opacity-muted
Secondary text emphasis, decorative icons, low-priority labels
$opacity-subtle
Partial dimming — caption text, soft helper icons, breadcrumb separators
$opacity-soft
Slight tint — hover ghost states, soft accent overlays, image overlays for legibility
$opacity-strong
Near-full visibility — prominent overlays, modal scrim fade-ins, header tinting

Icon & Avatar Sizes

Square dimensions for icons and avatars. The two scales intentionally overlap (24px and 32px appear in both) — use the --avatar-* tier when the element represents a person or profile so the intent is clear at the call site.

Icon Sizes
xs12px
sm16px
md20px
lg24px
xl32px
Avatar Sizes
A
xs24px
A
sm32px
A
md40px
A
lg52px
A
xl64px
$icon-size-xs / sm
Inline glyphs (12px), common UI icons inside buttons and form fields (16px)
$icon-size-md / lg
Subheader icons, table row indicators (20px); large icons in cards and FABs (24px)
$icon-size-xl
Extra-large icons for empty states, hero areas, and feature blocks (32px)
$avatar-xs / sm
Inline avatar in mentions and comment threads (24px); compact list rows and leaderboard rows (32px)
$avatar-md
Default avatar — feed cards, member rows, recognition lists (40px)
$avatar-lg / xl
Podium / top-3 leaderboard avatar (52px); profile header on settings page (64px)
Known tech debt

The clean scale above is the intended standard. Older components still use off-scale dimensions like 27px, 30px, 35px, 37px, 45px, and 90px for avatars. These predate the design-token system and should be migrated to the nearest token (e.g. 27px → --icon-size-lg 24px, 45px → --avatar-md 40px) once a designer has signed off on the small visual shifts.

Icons

Icons live in the dedicated VC Icon Library — ~3,500 icons (Lucide strokes + Tabler fills) with search, categories, stroke-width & colour controls, and an AI “create / find an icon” tool. In-app they render through the <vc-icon> component.

Open the VC Icon Library →

vantage-icon-library.pages.dev

Semantic Tokens

Assign a name to a meaning, not just a colour. Every UI state must use one of these — never a raw hex value. Source: color-tokens.md.

Text
--text-primary
#33475b
Headings, body copy, form input values, modal titles
Reward 1,200 points to your team
Employee recognition drives engagement and retention across all departments.
Form label value: john.doe@company.com
--text-secondary
#5c6c7c
Supporting labels, captions, metadata, timestamps
Last updated 2 hours ago · Category: Shopping
Valid until Dec 31, 2025  ·  500+ brands
Section Label / Helper Text
--color-white
#ffffff
Text / icons on dark or coloured backgrounds
Welcome back, Sarah
You have 3,400 points ready to redeem
Status — foreground & background (Bootstrap 4.6)
--color-success
#28a745
Confirmed, completed, positive
--color-success-bg
#d4edda
Success alert / banner bg
--color-error
#dc3545
Errors, destructive actions
--color-error-bg
#f8d7da
Error alert / banner bg
--color-warning
#ffc107
Caution — always use dark text on top
--color-warning-bg
#fff3cd
Warning alert bg
Interactive
--color-link
→ brand primary
Hyperlinks, text buttons, "View all" — underline on hover required
--color-focus
brand + rgba(…, 0.25) glow
Focus ring on all interactive elements — keyboard nav
Disabled (pair — always applied together)
--color-disabled-bg
#eef0f4 → $grey-divider
Background of disabled inputs, buttons, selects
--color-disabled-text
#637281 → $grey-icon
Text and icons inside any disabled control
Overlays
--overlay-modal
rgba(0,0,0,0.5)
Full-screen backdrop behind modals and drawers
--overlay-subtle
rgba(0,0,0,0.1)
Hover overlay on image tiles and card hover darkening
Brand accent — availability
--color-available
#6DD400
Availability dot / pill on product & swag cards. Not for success states.
--color-available-dark
#459D14
Availability label text beside the lime dot

Buttons

Uses brand, semantic, and neutral tokens. Border-radius: --radius-pill.

Variants
Sizes
Disabled state
With icon

Badges

Semantic status indicators. Background uses -50 tint, text uses -600.

With dot
Brand Active Expired Pending New Inactive
Text only
Premium Redeemed Rejected Under review Featured Draft

Alerts

Background uses -25, border uses -200, text uses -600.

🎉
You have 1,200 reward points!
Redeem them on 500+ brands before they expire on Dec 31.
ℹ️
New perks added this week
Check out 12 new deals from top brands available for redemption.
Reward sent successfully
Your team member will receive an email notification shortly.
⚠️
Points expiring soon
450 points will expire in 7 days. Redeem now to avoid losing them.
🚫
Redemption failed
Your request could not be processed. Please try again or contact support.

Inputs

Border: --neutral-300. Focus ring: --brand-500 at 12% opacity. Radius: --radius (8px).

Type a keyword to search
border: brand-500
Enter a valid email address
Looks good!
This field is read-only

Typography

8 sizes · 4 weights · 2 families · 2 line heights · 2 letter-spacings. Always use the token — never a raw value.

Font Size
$font-size-xs
11px
0.6875rem
Captions, timestamps, micro labels, table column headers in dense UI
$font-size-sm
12px
0.75rem
Button labels, form field labels, tags, small body text in dense UI
$font-size-dense
13px
0.8125rem
Table rows, sidebars, compact list items — 14px causes wrapping but 12px feels too small
$font-size-md DEFAULT
14px
0.875rem
Default body — paragraphs, table content, form input values, modal body
$font-size-lg
16px
1rem
Subheadings, card titles, section labels
$font-size-xl
18px
1.125rem
Section headings, modal titles, prominent panel headers
$font-size-2xl
20px
1.25rem
Page titles, most prominent heading on any given page
$font-size-3xl
26px
1.625rem
Congratulations Anthony Wood
Font Weight
Aa
$font-weight-regular
400 — Body copy, default
Aa
$font-weight-medium
500 — Buttons, labels, nav
Aa
$font-weight-semibold
600 — Section headings
Aa
$font-weight-bold
700 — Page titles, key numbers
Font Family
$font-family-base
'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif
The quick brown fox jumps over the lazy dog
$font-family-mono
ui-monospace, 'SF Mono', Consolas, monospace — gift card codes, employee IDs, copy-paste tokens
VC-2024-GIFTCARD-8X3K9
john.doe@company.com
Line Height
$line-height-tight · 1.2
Top Deals
This Week
Headings ($font-size-lg and above), single-line labels, buttons
$line-height-base · 1.5
Valid on all Amazon.in purchases. Reward points are non-transferable and expire within 12 months of issue.
Body copy, paragraphs, table cells, form text — the default, inherited everywhere
Letter Spacing
$letter-spacing-normal · 0
Top Deals This Week
Default — all body and heading text. Inter reads well at 0 tracking.
$letter-spacing-wide · 0.05em
Section Label · Active Status · New
ALL-CAPS labels, badges, eyebrow headings only — never sentence-case text

Spacing

8 steps — applies to padding, margin, and gap. Based on a 4px base with 5px-grid influence. Write 0 as a literal, never a token.

$space-14px
Micro inline gaps, icon-to-label gaps, badge internal padding
$space-28px
Compact gaps between tightly grouped elements, chip padding, tight gutters
$space-310px
Base element padding, standard vertical/horizontal margins on list items and table rows
$space-412px
Button padding (vertical), medium grid gap between form fields
$space-516px
Card inner padding on compact cards, vertical rhythm inside form sections
$space-620px
Standard card and section padding, default gap between stacked components
$space-724px
Generous card padding, wide grid gap, modal content padding
$space-832px
Section vertical rhythm, page container padding, large separation between major layout regions

Z-Index

15-step stacking hierarchy. Mid-tier values (1000-1090) follow Bootstrap's conventions so existing utility consumers stay aligned. Reach for the top tier (1100+) only for full-screen system overlays that must sit above modals. Never invent ad-hoc z-index: 9999 values — pick the right tier.

behind
$z-behind-1
Push below default flow — decorative background, pseudo-element underlay
base
$z-base0
Default stacking context — establishes a new layer without lifting it
raised
$z-raised1
Nudge above a sibling — captions over an image, focused field over peers
elevated
$z-elevated10
Component-internal layering — sticky thumbnails inside a carousel, badge over avatar
dropdown
$z-dropdown1000
Menu and select dropdowns — Bootstrap-aligned
sticky
$z-sticky1020
Sticky headers and subheaders — Bootstrap-aligned
fixed
$z-fixed1030
Fixed nav, scroll-to-top FAB, sticky footer — Bootstrap-aligned
overlay
$z-overlay1040
Modal backdrop, dimmed background — Bootstrap-aligned
offcanvas
$z-offcanvas1050
Side drawer, slide-out panel — Bootstrap-aligned
modal
$z-modal1060
Dialog, modal, full-page sheet — Bootstrap-aligned
popover
$z-popover1070
Popover, mention dropdown, contextual flyout — Bootstrap-aligned
tooltip
$z-tooltip1080
Tooltip — sits above popovers so hint text never gets clipped
toast
$z-toast1090
Toast, snackbar, banner notification — top of dismissable layer
tour
$z-tour1100
Joyride / onboarding overlay — must sit above all modals to guide the user
max
$z-max1200
Full-screen blocking overlay — holiday banners, splash screens, hard interrupts

Cards

Deal card
Amazon Gift Card
Valid on all Amazon.in products
Hot
Flipkart Voucher
Electronics, fashion & more
Wellness Package
Spa & wellness experiences
Stat card
Total Points Issued
48,200
↑ 12.4% this month
Redemptions
1,340
↓ 3.1% vs last
Active Users
892
↑ 8.7% this week

Components — Cards

Reward & recognition surfaces built during the design-system rollout. Static replicas; in-app these are Angular components.

Leaderboard podium <vc-leaderboard>

Brand-purple panel, bottom-aligned 2·1·3 columns (winner centred & larger), white avatar rings, medal rank seals, and a white laurel framing #1. Tokens: --company-color-hex, --radius-xl, --space-8; medal #dba91e/#a6b1c0/#bd7339.

2
Kamalika P.
CX (R&R)
1
Heather T.
Corporate Sales
3
Jaspreet S.
Development
<vc-leaderboard></vc-leaderboard> // .vc-podium · .vc-pcard · .vc-pcard__rankbadge
Wall of Fame card

White card: header with a confetti burst (top-right), a company-hex-ringed avatar carrying the medal seal, the recipient name in --company-color-hex (clickable) + role, and a round chevron “view details” button; below, a white Awards · Badges stats strip split by a --neutral-200 divider. Tokens: --shadow-sm, --radius-xl, avatar ring --border-width-thin --company-color-hex.

1
Subhendu Gogoi
Engineering
Awards
144
Badges
425
Awards
144
Badges
425
<vc-walloffame> // .vc-wof-card · .vc-wof-rank (medal seal)
Circle of Excellence tile

White card, confetti header (avatar + purple name + dept), divider, white body with recent-award medal + chevron. Tokens: --company-color-hex ring/name, --neutral-200 divider, --radius-xl.

Anurag Barkataki
Marketing (R&R)
<vc-circle-of-excellence-tile> // .vc-coe-card
My Ranking card

Leaderboard sidebar: brand-tint card, ringed avatar with medal seal (top-3 only), “You”, and a Current-Rank stat tile. Tokens: color-mix(--company-color-hex) tint, --radius-xl.

2
You
Current Rank
2
// .vc-myrank · .vc-myrank__badge (medal seal, rank 1-3)

Components — Modals

Dialog surfaces. All share the soft gradient header, generous body padding, and the Lucide-X close button.

Award-details modal (Circle of Excellence)

Centred header stack over a centred 2-up award grid; each award is a split card (neutral medal top, white title/points body) — mirrors the “By Awards” layout. Tokens: gradient header, --neutral-50 card top, --radius-lg.

Anurag’s Awards

Marketing (R&R) · 12 awards

Star Performer
500 pts
Team Player
300 pts
// vc-modal-md · award-details-dialog · .vc-award-grid
Profile-photo edit dialog

Avatar hugged by a pill card; the AI-style sparkle sits on the avatar’s bottom-right edge (left/top:84%). Primary “Upload” + destructive text-link “Delete Photo”. Tokens: --radius-full, --error-600.

Edit Profile Photo

// profile-pic-modification · .profile-preview-card · .ai_styl
Likes / receivers list

Avatar → name rows with a fixed gap (the global p-reset excludes this dialog so the name keeps its spacing from the avatar). Tokens: --radius-full avatars, --neutral-200 header rule.

Liked by 3
Subhendu Gogoi
Engineering
Kamalika Phukan
CX (R&R)
Heather Tuffnail
Corporate Sales
// feed-receivers-dialog (p-reset excluded in styles.scss)

Components — Feed preview card

Recognition “preview your post” card. Mirrors the vc-feed v9 post layout: header (recipient + “appreciated by” + time + menu), optional 2-line-clamped caption, award/photo body, hashtags, Like/Comment footer.

Tokens: --radius-xl, --shadow-md, caption font-size:var(--font-size-md)/line-height:1.5, award block --neutral-50, hashtags & action icons --company-color-hex.

Heather Tuffnail was appreciated by Anurag Barkataki

Just now

Consistently goes above and beyond for the team — thank you for the brilliant work this quarter! 🎉

Star Performer

Heather Tuffnail

#teamwork#excellence
Like Comment
<vc-preview-feed> // .fp-card · .fp-header · .fp-award · .fp-footer

Components — Badges & pills

Small shared elements used across the recognition surfaces.

Medal rank seal (1 · 2 · 3)

Scalloped “seal” badge for top-3 ranks. Built from a SVG mask (centre circle + 8 perimeter bumps) layered as pseudo-elements — an unmasked base carries the drop-shadow, a masked gradient fills the seal, and a slightly-larger masked white layer draws the stroke (a mask clips drop-shadow, so the stroke must be a backing layer, not a shadow). Medal palette: gold #dba91e, silver #a6b1c0, bronze #bd7339.

1 2 3
// .vc-pcard__rankbadge / .vc-wof-rank / .vc-myrank__badge — shared seal mask + layered ::before/::after
Header wallet pill

Capsule in the global header: wallet icon + a vertically-centred “Vantage Points / value” stack, with the profile avatar alongside. Value is a <span> (never a nested <a> — that produced invalid HTML and broke centring). Tokens: --radius-pill, --shadow-xs, brand-tint bg.

Vantage Points12,480
// module-options · .vc-header-pill · .wallet-label / .wallet-value (span)

Foundation Principles

The non-negotiables. Every rule later in this document is downstream of one of these.

  1. Tokens, not literals. Never write a raw hex, px, rem, or rgba in a component. If a token doesn't exist for the value you need, the value is wrong — pick the nearest token instead of inventing a new one.
  2. Semantic over primitive. Reach for --text-primary before --neutral-600, --color-error before --error-500. Primitives are for defining tokens; semantics are for using them.
  3. One way to do each thing. A button is a .btn-* class, not a hand-rolled border-radius + padding. A card is .card, not bespoke shadow + radius. Variants exist; reinventions don't.
  4. No !important. It is the symptom of a specificity problem, not a fix. If you need it, you're styling at the wrong layer — fix the cascade or scope the selector instead.
  5. Brand colour is one ramp, not seven shades of purple. All purple comes from the --brand-* ramp. Never hand-pick a "slightly different purple" — use the next step on the ramp.
  6. Light by default, dark by intention. Surfaces are white or --neutral-50/100. Dark sections are reserved for trust strips, testimonials, gradient CTAs, and the footer — and never two in a row.
  7. Accent colour is loud — use it like punctuation. Status colours (success, error, warning, info) communicate state. They never decorate. They never live alongside other status colours in the same component.
  8. Mobile-first responsive. Every layout is designed for a 320–375 px viewport first, then upscaled at sm, md, lg, xl. If it doesn't work on mobile, it isn't done.

Section Layout Patterns

Every content section is built from one of these skeletons. Copy-paste, don't reinvent.

15.1 Standard Section
Heading + optional subtext + grid of cards + single outline CTA. The default layout for any feature section.
<section class="section"> <div class="container"> <h2 class="section-heading">Section heading</h2> <p class="section-subtext">Optional subtext, max ~60 chars.</p> <div class="section-grid"> <!-- cards --></div> <div class="section-cta"> <a href="#" class="btn btn-outline">Action label</a> </div> </div> </section>
15.2 Two-Column Card Grid
Side-by-side cards on desktop, single stack on mobile.
<div class="grid grid-2"> <article class="card card-compact">…</article> <article class="card card-compact">…</article> </div>
15.3 Stat Grid (5-column metrics)
5 cards on desktop, 2 columns on mobile with the last card spanning both.
.stat-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-3); } @media (min-width: 900px) { .stat-grid { grid-template-columns: repeat(5, 1fr); } } .stat-grid > :nth-child(5) { grid-column: span 2; max-width: 50%; justify-self: center; }
15.4 Dark CTA Section
Brand-dark card with white heading + outline-white or neutral CTA.
<div class="card card-dark cta-dark"> <div> <h2 class="section-heading-light">Heading</h2> <p class="section-subtext-light">Description</p> </div> <a href="#" class="btn btn-neutral">Action</a> </div>
15.5 Brand-Surface Feature Section
Warm promotional sections — use --brand-25 or --brand-50 as section background.
.section-brand { background: var(--brand-25); }

Page Anatomy

A typical page rhythm. Not every section is required — but light/dark alternation is.

Headersticky, white
Herowhite · h1 + subtext + dual CTA + hero image
Benefits Bar--neutral-50 · 4-column icon + label compact cards
Trust Stripwhite · "Trusted by" + scrolling logos
Feature Sectionwhite · heading + standard cards + outline CTA
Tabbed / Carousel--neutral-50 · desktop tabs · mobile snap-carousel
Testimonial--brand-600 · quote + video thumbnail + dots
Awardswhite · compact white cards with badge images
Dark Strip--brand-600 · compact info / security bar
Gradient CTA--brand-500 → --brand-600 · heading + CTA + image
Stat Gridwhite · 5-column metric cards
Footer--neutral-600

Rhythm Rules

  1. Alternate light ↔ dark — never stack two darks
  2. White is the default surface
  3. One CTA per section; outline by default; only the hero gets the filled .btn-primary
  4. Section headings are centred with max-width: 56ch to prevent long lines
  5. Every section is self-contained — <section class="section"> with its own .container

Responsive Behavior

Mobile-first. Designed for 320–375 px viewport, then upscaled.

Grid Collapse

DesktopMobile fallback
grid-cols-3Single-column stack
grid-cols-2Single-column stack
grid-cols-5 (stat grid)grid-cols-2, last card spans 2
Side-by-side card+imageImage below text

Mobile-Specific Treatments

Mobile Carousel Pattern

For 3+ items that don't fit a stacked layout, use a snap carousel on mobile, grid on desktop.

.carousel-mobile { display: flex; gap: var(--space-5); overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; } .carousel-card { flex: 0 0 100%; scroll-snap-align: center; } @media (min-width: 1024px) { .carousel-mobile { display: grid; grid-template-columns: repeat(3, 1fr); } }

Image & Media Treatment

Rules for loading, sizing, and responsive image patterns.

Loading & Format

Image Sizing

ContextPattern
Logos in cardsFixed width, no explicit height
Card feature imagesmax-width: 100%; max-height: 100%; object-fit: contain
Avatarsborder-radius: var(--radius-pill), fixed width = height
Award badgesmax-width: 130px, object-fit: contain, height: auto
Trust logosFixed height 40px, auto width, horizontal scroll on mobile

Dark Section Rules

Dark surfaces are reserved for trust strips, testimonials, gradient CTAs, and the footer. Light by default, dark by intention.

Live example — dark section

Body text uses rgba(255,255,255,0.7), never --text-primary (which is invisible on dark).

.btn-neutral

When to Go Dark

SurfaceUse
--brand-600Compact info bars, trust strips, single-row content
--neutral-600Footer, mature content sections
Brand gradient (--brand-500 → --brand-600)Feature showcases, CTA banners

What's Allowed on Dark

ElementTreatment
Headings--color-white
Bodyrgba(255,255,255,0.7)
Mutedrgba(255,255,255,0.5)
Buttons.btn-neutral (white-fill) or .btn-outline with white border + white text
Links--color-white, no underline by default — text-decoration: underline on hover
Icons / badgesrgba(255,255,255,0.08) container background

Don'ts on Dark

Motion & Transitions

100+ uses of all 0.3s ease-in-out showed up in the audit with no token. Standardised below. Hover the demo boxes to feel each duration.

--duration-fast
150 ms — hover, tooltip
--duration-base
200 ms — default
--duration-slow
300 ms — card lift, modal
--duration-slower
500 ms — carousel, hero
--ease-bounce
300 ms · pop · optional

Duration

TokenValueUsage
--duration-fast150 msHover colour shifts, focus rings, tooltip fade
--duration-base200 msDefault — buttons, inputs, dropdowns
--duration-slow300 msCard hover lift, modal open, drawer slide
--duration-slower500 msCarousel slide, hero fade-in

Easing

TokenValueUsage
--ease-outcubic-bezier(0.16, 1, 0.3, 1)Entrance — modals, dropdowns, toasts
--ease-in-outcubic-bezier(0.65, 0, 0.35, 1)Default — most state changes
--ease-bouncecubic-bezier(0.34, 1.56, 0.64, 1)Optional pop — chip select, success checkmark
Never use transition: all. Always enumerate properties — the audit shows all is the single biggest cause of unintended re-renders and layout flicker.

Design Rules

The five rules everyone breaks first.

  1. Brand colour is --brand-500. Period. Don't mix in raw #5b3b97 even though it's "the same". Use the token, so the runtime override works.
  2. No pure grey. All neutrals have a slight blue cast. Replacing #999 or #666 with the right --neutral-* step is almost always correct.
  3. One <h1> per page. Heading levels are semantic — never skip from <h2> to <h4> for visual reasons. If you need a smaller heading, use a smaller font-size token.
  4. Status colours don't decorate. --color-success is for state, not for "this is good news". A green-bordered card is wrong unless the card represents a successful state.
  5. Section padding is --space-8 vertical, container --space-5/8 horizontal. No bespoke values. The rhythm is the rhythm.

Common Mistakes & Fixes

Highest-frequency violations from the codebase audit. Use this as a code review checklist.

❌ Mistake✅ Fix
#fff or #ffffff written inlineUse var(--color-white)
#33475b or any hex matching a tokenUse matching --text-* / --neutral-* token
Multiple values for same role (4 different success greens)Collapse to var(--color-success) (#28a745)
Pure grey (#999, #666, #ccc, #000)Replace with bluish-grey via --neutral-* ramp
!important to override a parent styleFix the cascade — scope the selector or move the rule into the right layer
Hardcoded 14px font sizeUse var(--font-size-md)
Hardcoded 0.7rem font sizeUse var(--font-size-sm) (12) — drop 0.7rem entirely
Missing font-weightAlways set explicitly with var(--font-weight-*)
font-family: Inter (unquoted)Always var(--font-family-base)
border-radius: 16px literalUse var(--radius-xl)
border-radius: 25px literal (button)Use var(--radius-pill)
border-radius: 8px literal (input)Use var(--radius)
Custom shadow (0px 3px 6px #00000029)Snap to nearest --shadow-* token
transition: all 0.3s ease-in-outEnumerate properties; use --duration-slow + --ease-in-out
Square buttons (--radius-md/lg)All buttons are --radius-pill
Buttons with no focus ringAdd box-shadow: 0 0 0 4px var(--color-focus) on :focus-visible
Heading text in --brand-500Headings stay --text-primary. Brand colour is for CTAs and links
Body paragraph in --brand-* or status colourBody is always --text-primary (or --text-secondary)
text-transform: uppercase on buttonsSentence case only
Multiple <h1> on one pageExactly one <h1> per page, in the hero
Image without loading="lazy" (below fold)Add lazy loading to all non-hero images
Decorative image with descriptive altalt="" and role="presentation"
Section without .container wrapperWrap content in <div class="container">
Two dark sections back-to-backInsert a light section or trust strip between them
.btn-primary on a dark surfaceUse .btn-neutral (white fill) or outline-white instead

CSS Variable Quick-Reference

Copy-paste root for any new project or audit baseline. Click each block to expand.

Brand (runtime override-able)
--brand-25:  #f6f4fb;
--brand-50:  #f1edf8;
--brand-100: #e2daf1;
--brand-200: #c5b5e2;
--brand-300: #a086d0;
--brand-400: #7a56bd;
--brand-500: #5b3b97;
--brand-600: #472e75;
Neutrals
--neutral-50:  #f5f5f7;
--neutral-100: #eff2f5;
--neutral-200: #eef0f4;
--neutral-300: #d9dde7;
--neutral-400: #637281;
--neutral-500: #5c6c7c;
--neutral-600: #475467;
Semantic text + status
--text-primary:    #33475b;
--text-secondary:  var(--neutral-500);
--color-white:     #ffffff;
--color-success:   #28a745;
--color-error:     #dc3545;
--color-warning:   #ffc107;
--color-link:      var(--brand-500);
--color-focus:     rgba(var(--company-color), 0.25);
--color-disabled-bg:   #eef0f4;
--color-disabled-text: #637281;
Spacing & layout
--space-1: 4px;
--space-2: 8px;
--space-3: 10px;
--space-4: 12px;
--space-5: 16px;
--space-6: 20px;
--space-7: 24px;
--space-8: 32px;
Border radius & widths
--radius-none: 0;
--radius-2xs:  2px;
--radius-xs:   4px;
--radius-sm:   6px;
--radius:      8px;
--radius-md:   10px;
--radius-lg:   12px;
--radius-xl:   16px;
--radius-pill: 50px;

--border-width-hairline: 0.5px;
--border-width-thin:     1px;
--border-width-md:       2px;
--border-width-thick:    3px;
--border-width-heavy:    5px;
Typography
--font-family-base: 'Inter', system-ui, sans-serif;

--font-size-xs:    11px;
--font-size-sm:    12px;
--font-size-dense: 13px;
--font-size-md:    14px;
--font-size-lg:    16px;
--font-size-xl:    18px;
--font-size-2xl:   20px;
--font-size-3xl:   26px;

--font-weight-regular:  400;
--font-weight-medium:   500;
--font-weight-semibold: 600;
--font-weight-bold:     700;
Shadows (uniform 0.10 alpha on --shadow-color-rgb)
--shadow-color-rgb: 71, 84, 103;
--shadow-xs:  0px 1px 2px rgba(var(--shadow-color-rgb), 0.10);
--shadow-sm:  0px 1px 3px rgba(…,0.10), 0px 1px 2px rgba(…,0.10);
--shadow-md:  0px 4px 8px rgba(…,0.10), 0px 2px 4px rgba(…,0.10);
--shadow-lg:  0px 12px 16px rgba(…,0.10), 0px 4px 6px rgba(…,0.10);
--shadow-xl:  0px 20px 24px rgba(…,0.10), 0px 8px 8px rgba(…,0.10);
--shadow-2xl: 0px 24px 48px rgba(var(--shadow-color-rgb), 0.10);
Motion & opacity
--duration-fast:   150ms;
--duration-base:   200ms;
--duration-slow:   300ms;
--duration-slower: 500ms;

--ease-out:    cubic-bezier(0.16, 1, 0.3, 1);
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
--ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);

--opacity-faint:    0.3;
--opacity-disabled: 0.4;
--opacity-muted:    0.6;
--opacity-subtle:   0.7;
--opacity-soft:     0.8;
--opacity-strong:   0.9;
Z-index (Bootstrap-compatible)
--z-behind:    -1;
--z-base:       0;
--z-raised:     1;
--z-elevated:   10;

--z-dropdown:   1000;
--z-sticky:     1020;
--z-fixed:      1030;
--z-overlay:    1040;
--z-offcanvas:  1050;
--z-modal:      1060;
--z-popover:    1070;
--z-tooltip:    1080;
--z-toast:      1090;

--z-tour:       1100;
--z-max:        1200;

Token Migration Cheatsheet

Use when refactoring existing SCSS or replacing literals during code review.

Color

Old literalNew token
#fff, #ffffff, whitevar(--color-white)
#33475b, $text-primaryvar(--text-primary)
#5c6c7c, $text_secondaryvar(--text-secondary)
#d9dde7, $border-colorvar(--neutral-300)
#eff2f5, $base_color, $sec-bgvar(--neutral-100)
#eef0f4, $border-bottomvar(--neutral-200)
#637281, rgb(99,114,129), $icon-colorvar(--neutral-400)
#5b3b97, --company-color-hex, rgba(91,59,151,1)var(--brand-500)
#28a745, #389704, #6DD400, #459D14var(--color-success)
#dc3545, redvar(--color-error)
#ffc107var(--color-warning)
#17a2b8var(--info-500)
#6c757d, #adb5bd, #ced4davar(--color-disabled-text)
rgba(0,0,0,0.5)var(--overlay-modal)
rgba(0,0,0,0.1)var(--overlay-subtle)

Typography

Old literalNew token
14px, $normalvar(--font-size-md)
12px, 0.7remvar(--font-size-sm)
13px, 0.8remvar(--font-size-dense)
15px, 16px, 1remvar(--font-size-lg)
18pxvar(--font-size-xl)
20pxvar(--font-size-2xl)
Inter, "Inter", Robotovar(--font-family-base)
font-weight: 500var(--font-weight-medium)
font-weight: 700var(--font-weight-bold)

Radius

Old literalNew token
2pxvar(--radius-2xs)
4pxvar(--radius-xs)
5px, 6pxvar(--radius-sm)
8pxvar(--radius)
10pxvar(--radius-md)
12pxvar(--radius-lg)
15px, 16pxvar(--radius-xl)
25px, 9999px, 4.4375rem (button)var(--radius-pill)
50% (true circle)var(--radius-pill) with equal width = height

Shadow

Old literalNew token
0px 3px 6px #00000029, $box-shadowvar(--shadow-sm)
0px 3px 5px 0px rgb(0 0 0 / 21%)var(--shadow-md)
0px 3px 20px 0px rgb(0 0 0 / 10%)var(--shadow-lg)
rgba(0, 0, 0, 0.15) 0px 5px 15px 0pxvar(--shadow-xl)
0 4px 6px 0 rgba(120,135,150,0.20)var(--shadow-md)

Transition

Old literalNew tokens
all 0.3s ease-in-outEnumerate props + var(--duration-slow) var(--ease-in-out)
all 0.2s ease-in-outEnumerate props + var(--duration-base) var(--ease-in-out)
all 0.5s cubic-bezier(...)Pick var(--duration-slower) + var(--ease-out) or --ease-bounce