Styling Guide¶
Overview¶
This guide defines the standard color system, typography, and component patterns for all Next.js applications in the homelab. All colors meet WCAG AA contrast requirements (4.5:1 for normal text, 3:1 for large text).
Color System¶
Semantic Color Palette¶
Use these semantic classes instead of raw Tailwind colors to ensure consistent contrast:
Text Colors¶
text-primary → text-gray-900 (primary content, headings)
text-secondary → text-gray-600 (secondary content, labels)
text-tertiary → text-gray-500 (tertiary content, captions)
text-disabled → text-gray-400 (disabled state)
text-inverse → text-white (on dark backgrounds)
Background Colors¶
bg-canvas → bg-gray-50 (page background)
bg-surface → bg-white (card/panel background)
bg-subtle → bg-gray-100 (hover states, subtle backgrounds)
Interactive Colors (Blue Theme)¶
Primary (Blue):
text-interactive → text-blue-600 (links, interactive text)
bg-interactive → bg-blue-600 (primary buttons)
bg-interactive-hover → bg-blue-700 (primary button hover)
bg-interactive-subtle → bg-blue-50 (info backgrounds)
border-interactive → border-blue-300 (focus rings, interactive borders)
Status Colors¶
Success (Green):
bg-success → bg-green-50 (success background)
border-success → border-green-200 (success border)
text-success → text-green-900 (success text - dark for contrast)
text-success-icon → text-green-600 (success icons)
Error (Red):
bg-error → bg-red-50 (error background)
border-error → border-red-200 (error border)
text-error → text-red-900 (error text - dark for contrast)
text-error-icon → text-red-600 (error icons)
Warning (Yellow):
bg-warning → bg-yellow-50 (warning background)
border-warning → border-yellow-200 (warning border)
text-warning → text-yellow-900 (warning text - dark for contrast)
text-warning-icon → text-yellow-600 (warning icons)
Info (Blue):
bg-info → bg-blue-50 (info background)
border-info → border-blue-200 (info border)
text-info → text-blue-900 (info text - dark for contrast)
text-info-icon → text-blue-600 (info icons)
Typography¶
Headings¶
h1: text-3xl font-bold text-primary
h2: text-2xl font-bold text-primary
h3: text-xl font-semibold text-primary
h4: text-lg font-semibold text-primary
Body Text¶
Form Labels¶
Component Patterns¶
Buttons¶
Primary:
<button className="bg-interactive text-inverse px-4 py-2 rounded-lg hover:bg-interactive-hover disabled:opacity-50 disabled:cursor-not-allowed">
Primary Action
</button>
Secondary:
<button className="border border-gray-300 text-primary px-4 py-2 rounded-lg hover:bg-subtle disabled:opacity-50">
Secondary Action
</button>
Destructive:
<button className="bg-red-600 text-white px-4 py-2 rounded-lg hover:bg-red-700 disabled:opacity-50">
Delete
</button>
Ghost:
Form Inputs¶
Text Input:
<input
type="text"
className="w-full border border-gray-300 rounded-lg px-3 py-2 text-gray-900 placeholder:text-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Enter text..."
/>
Text Area:
<textarea
className="w-full border border-gray-300 rounded-lg px-3 py-2 text-gray-900 placeholder:text-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
rows={3}
/>
Select:
<select className="border border-gray-300 rounded-lg px-3 py-2 text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500">
<option>Option 1</option>
</select>
Alert Boxes¶
Success:
<div className="bg-success border border-success rounded-lg p-4">
<div className="flex items-start gap-2">
<CheckCircle className="w-5 h-5 text-success-icon flex-shrink-0 mt-0.5" />
<div className="flex-1">
<h3 className="font-medium text-success mb-1">Success Title</h3>
<p className="text-sm text-success">Success message text</p>
</div>
</div>
</div>
Error:
<div className="bg-error border border-error rounded-lg p-4">
<div className="flex items-start gap-2">
<AlertCircle className="w-5 h-5 text-error-icon flex-shrink-0 mt-0.5" />
<div className="flex-1">
<h3 className="font-medium text-error mb-1">Error Title</h3>
<ul className="text-sm text-error space-y-1">
<li>• Error item 1</li>
<li>• Error item 2</li>
</ul>
</div>
</div>
</div>
Warning:
<div className="bg-warning border border-warning rounded-lg p-4">
<div className="flex items-start gap-2">
<AlertTriangle className="w-5 h-5 text-warning-icon flex-shrink-0 mt-0.5" />
<div className="flex-1">
<h3 className="font-medium text-warning mb-1">Warning Title</h3>
<p className="text-sm text-warning">Warning message text</p>
</div>
</div>
</div>
Info:
<div className="bg-info border border-info rounded-lg p-4">
<div className="flex items-start gap-2">
<Info className="w-5 h-5 text-info-icon flex-shrink-0 mt-0.5" />
<div className="flex-1">
<h3 className="font-medium text-info mb-1">Info Title</h3>
<p className="text-sm text-info">Info message text</p>
</div>
</div>
</div>
Cards¶
Standard Card:
<div className="bg-surface rounded-lg shadow-sm border border-gray-200 p-6">
<h3 className="text-xl font-semibold text-primary mb-2">Card Title</h3>
<p className="text-secondary">Card content</p>
</div>
Modals/Dialogs¶
Modal Container:
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
<div className="bg-surface rounded-lg max-w-2xl w-full p-6 max-h-[90vh] overflow-y-auto">
<div className="flex items-center justify-between mb-4">
<h2 className="text-2xl font-bold text-primary">Modal Title</h2>
<button className="text-secondary hover:text-primary">
<X className="w-6 h-6" />
</button>
</div>
{/* Modal content */}
</div>
</div>
Accessibility Guidelines¶
Contrast Requirements¶
- Normal text (< 18pt): Minimum 4.5:1 contrast ratio
- Large text (≥ 18pt or 14pt bold): Minimum 3:1 contrast ratio
- UI components: Minimum 3:1 contrast ratio
Safe Color Combinations¶
White/Light Backgrounds: - ✅ text-gray-900, text-gray-800 (primary content) - ✅ text-gray-600 (secondary content) - ✅ text-blue-600, text-blue-700 (links/interactive) - ❌ text-gray-400, text-gray-500 (too light for normal text) - ❌ text-blue-400, text-blue-500 (too light on white)
Colored Backgrounds (50 variants like bg-green-50): - ✅ text-{color}-900 (always safe) - ✅ text-{color}-800 (safe for normal text) - ⚠️ text-{color}-700 (use for large text only or icons) - ❌ text-{color}-600 and lighter (insufficient contrast)
Dark Backgrounds (600+ variants): - ✅ text-white, text-gray-100 (primary) - ✅ text-gray-300 (secondary)
Common Mistakes to Avoid¶
- ❌ WRONG:
text-green-700onbg-green-50 -
✅ CORRECT:
text-green-900onbg-green-50 -
❌ WRONG:
text-gray-400for body text on white -
✅ CORRECT:
text-gray-600ortext-gray-900 -
❌ WRONG:
placeholder:text-gray-400on light backgrounds -
✅ CORRECT:
placeholder:text-gray-500orplaceholder:text-gray-600 -
❌ WRONG: Mixing color themes (indigo + blue)
-
✅ CORRECT: Pick one primary color (blue) and stick with it
-
❌ WRONG: Raw color classes everywhere
- ✅ CORRECT: Use semantic class patterns from this guide
Quick Reference: Text on Backgrounds¶
| Background | Body Text | Large Text/Icons |
|---|---|---|
bg-white |
text-gray-900 |
text-gray-600 |
bg-gray-50 |
text-gray-900 |
text-gray-600 |
bg-blue-50 |
text-blue-900 |
text-blue-700 |
bg-green-50 |
text-green-900 |
text-green-700 |
bg-red-50 |
text-red-900 |
text-red-700 |
bg-yellow-50 |
text-yellow-900 |
text-yellow-700 |
bg-blue-600 |
text-white |
text-white |
bg-gray-900 |
text-white |
text-gray-300 |
Testing Contrast¶
Use browser dev tools or online tools: - Chrome DevTools: Inspect element → Accessibility pane shows contrast ratio - Online: https://webaim.org/resources/contrastchecker/ - VS Code Extension: "Color Highlight" shows contrast ratios inline
Future: Tailwind v4 Theme Config¶
When migrating to Tailwind v4 CSS variables, define semantic tokens:
@theme {
--color-text-primary: #111827; /* gray-900 */
--color-text-secondary: #4B5563; /* gray-600 */
--color-text-tertiary: #6B7280; /* gray-500 */
--color-bg-canvas: #F9FAFB; /* gray-50 */
--color-bg-surface: #FFFFFF;
--color-interactive: #2563EB; /* blue-600 */
--color-interactive-hover: #1D4ED8; /* blue-700 */
--color-success: #065F46; /* green-900 */
--color-error: #991B1B; /* red-900 */
}
Then use: text-[--color-text-primary] instead of text-gray-900.