8.6 KiB
WorkRoot Component Library
A reference for all reusable UI components. Every component listed here fully supports dark/light theme switching via Tailwind's dark: variant prefix — the dark class is toggled on <html> by ThemeToggle.astro.
Theme Architecture
| Mechanism | Detail |
|---|---|
| Strategy | Tailwind class dark mode (darkMode: 'class' in tailwind.config.mjs) |
| Toggle | <ThemeToggle /> writes dark / light to localStorage and toggles html.dark |
| FOUC prevention | Inline <script is:inline> in BaseLayout.astro <head> reads localStorage synchronously before CSS renders |
| CSS tokens | src/styles/design-tokens.css + legacy aliases in global.css under html.dark {} |
Components
Badge
File: src/components/ui/Badge.astro
Eyebrow labels used above section headings.
<Badge label="Why Choose Us" variant="primary" />
<Badge label="New Feature" variant="accent" />
<Badge label="Category" variant="secondary" />
| Variant | Light | Dark |
|---|---|---|
primary (default) |
bg-primary-50 text-primary-700 |
bg-primary-900/40 text-primary-300 |
primary-dark |
bg-primary-400/20 text-primary-300 |
same (designed for dark sections) |
accent |
bg-accent-50 text-accent-700 |
bg-accent-900/40 text-accent-300 |
secondary |
bg-secondary-100 text-secondary-700 |
bg-secondary-800 text-secondary-300 |
Usage note: Use
primaryoraccenton light/surface sections. Useprimary-darkon hero/CTA sections that have dark gradient backgrounds (these sections are dark in both themes).
Card
File: src/components/ui/Card.astro
Versatile content container with predefined style variants.
<Card variant="feature">...</Card>
<Card variant="service" padding="p-8">...</Card>
<Card variant="surface">...</Card>
<Card variant="dark">...</Card> <!-- for use on dark gradient sections -->
<Card variant="plain">...</Card> <!-- no styles applied -->
| Variant | Light | Dark |
|---|---|---|
feature |
from-secondary-50 to-white, border secondary-100 |
from-secondary-800 to-secondary-900, border secondary-700 |
service |
bg-white, border secondary-100 |
bg-secondary-800, border secondary-700 |
surface |
bg-secondary-50, border secondary-200 |
bg-secondary-800, border secondary-700 |
dark |
bg-white/5 glassmorphism (static) |
same — designed for dark gradient backgrounds only |
SectionHeader
File: src/components/ui/SectionHeader.astro
Standard eyebrow + heading + description pattern used in every section.
<!-- On light/surface sections -->
<SectionHeader
badge="Our Services"
title="What We Build"
description="From concept to production."
theme="light"
badgeColor="primary"
/>
<!-- On dark/hero sections -->
<SectionHeader
badge="How It Works"
title="Simple Process"
theme="dark"
badgeColor="accent"
/>
| Prop | Type | Default | Notes |
|---|---|---|---|
badge |
string |
required | Eyebrow label text |
title |
string |
required | <h2> heading |
description |
string |
— | Optional paragraph below heading |
theme |
'light' | 'dark' |
'light' |
Controls text/badge colors |
badgeColor |
'primary' | 'accent' |
'primary' |
Badge hue |
maxWidth |
string |
'max-w-3xl' |
Tailwind width class |
Theme behavior:
theme="light"— headingtext-secondary-900 dark:text-secondary-50, descriptiontext-secondary-600 dark:text-secondary-400theme="dark"— headingtext-white, descriptiontext-secondary-300(used on dark gradient backgrounds that look the same in both themes)
ThemeToggle
File: src/components/ThemeToggle.astro
Sun/moon icon button that toggles between dark and light mode.
<ThemeToggle />
- Persists preference in
localStorageunder keytheme('dark'|'light') - Falls back to
prefers-color-schemewhen no stored preference - Updates
meta[name="theme-color"]for browser chrome color - Fully keyboard accessible — ARIA label updates dynamically
- Respects
prefers-reduced-motion
Header
File: src/components/Header.astro
Fixed top navigation with desktop menu, mobile slide-out panel, and theme toggle.
<Header /> <!-- used in BaseLayout, no props needed -->
Dark mode classes:
| Element | Light | Dark |
|---|---|---|
| Header bar | bg-white/95 border-secondary-100 |
bg-secondary-900/95 border-secondary-800 |
| Active nav link | text-primary bg-primary-50 |
text-primary-400 bg-primary-900/30 |
| Inactive nav link hover | hover:bg-secondary-50 |
dark:hover:bg-secondary-800 |
| Mobile panel | bg-white |
bg-secondary-900 |
| Mobile nav items | same pattern as desktop | same dark variants |
Footer
File: src/components/Footer.astro
Always dark (bg-secondary-900) — no light/dark switching needed as the footer is intentionally dark in both modes.
LazyImage
File: src/components/LazyImage.astro
Lightweight image wrapper with native lazy loading and fade-in animation.
<LazyImage
src="/images/hero.jpg"
alt="Team working"
width={1200}
height={630}
loading="eager"
fetchpriority="high"
/>
| Prop | Default | Notes |
|---|---|---|
loading |
'lazy' |
Use 'eager' for above-the-fold images |
fetchpriority |
'auto' |
Use 'high' for LCP images |
placeholder |
var(--color-surface-alt) |
Adapts to theme via CSS custom property |
The placeholder color uses --color-surface-alt from global.css, which is #f1f5f9 in light mode and #1e293b in dark mode.
Global CSS Component Classes
Classes defined in src/styles/global.css under @layer components. All support dark mode via dark: variants.
Buttons
<button class="btn-primary">Primary Action</button>
<button class="btn-primary-lg">Large CTA</button>
<button class="btn-secondary">Secondary</button>
<button class="btn-accent">Accent</button>
<button class="btn-ghost">Ghost (for dark sections)</button>
<button class="btn-inverse-lg">Inverse Large (for dark sections)</button>
| Class | Theme behavior |
|---|---|
btn-primary |
Cyan background — same in both modes (brand color) |
btn-secondary |
border-secondary in light / border-secondary-400 text-secondary-300 in dark |
btn-ghost |
bg-white/5 — designed for dark sections only |
Badges (global classes)
<span class="badge-primary">Label</span>
<span class="badge-accent">Label</span>
<span class="badge-secondary">Label</span>
<span class="badge-primary-dark">Label</span>
Cards (global classes)
<div class="card">...</div>
<div class="card-hover">...</div>
<div class="card-service">...</div>
<div class="card-surface">...</div>
Forms
<label class="form-label">Email</label>
<input class="form-input" type="email" />
<p class="form-error">Required field</p>
| Class | Dark mode |
|---|---|
form-input |
bg-secondary-800 border-secondary-600 text-secondary-100 |
form-label |
text-secondary-300 |
form-error |
text-red-400 |
Section Typography
<h2 class="section-title">Heading</h2>
<p class="section-description">Supporting text</p>
<!-- On dark gradient sections -->
<h2 class="section-title-inverse">Heading</h2>
<p class="section-description-inverse">Supporting text</p>
Design Tokens Quick Reference
Defined in src/styles/design-tokens.css and aliased in global.css.
| Token | Light value | Dark value |
|---|---|---|
--color-surface |
#f8fafc |
#0f172a |
--color-surface-alt |
#f1f5f9 |
#1e293b |
--color-border |
#e2e8f0 |
#334155 |
--color-text-primary |
#1e293b |
#f1f5f9 |
--color-text-secondary |
#475569 |
#cbd5e1 |
--color-text-muted |
#94a3b8 |
#94a3b8 |
--color-primary |
#0891b2 (cyan-600) |
#22d3ee (cyan-400) |
--color-accent |
#f59e0b (amber-500) |
#fbbf24 (amber-400) |
Adding a New Component
- Use
dark:prefixed Tailwind classes for all color utilities - For surface backgrounds:
bg-white dark:bg-secondary-800orbg-secondary-50 dark:bg-secondary-800 - For borders:
border-secondary-100 dark:border-secondary-700 - For text:
text-secondary-900 dark:text-secondary-50(headings),text-secondary-600 dark:text-secondary-400(body) - Do NOT use
html.darkCSS overrides — prefer Tailwinddark:variants for colocation - Use CSS custom properties (
var(--color-surface)) only when Tailwind classes won't work (e.g. dynamic inline styles)