Skip to content

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

body-lg:   text-base text-primary
body:      text-sm text-primary
body-sm:   text-xs text-secondary

Form Labels

label:     text-sm font-medium text-gray-800
helper:    text-xs text-secondary
error:     text-sm text-error

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:

<button className="text-secondary px-3 py-2 rounded-lg hover:bg-subtle">
  Cancel
</button>

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

  1. ❌ WRONG: text-green-700 on bg-green-50
  2. ✅ CORRECT: text-green-900 on bg-green-50

  3. ❌ WRONG: text-gray-400 for body text on white

  4. ✅ CORRECT: text-gray-600 or text-gray-900

  5. ❌ WRONG: placeholder:text-gray-400 on light backgrounds

  6. ✅ CORRECT: placeholder:text-gray-500 or placeholder:text-gray-600

  7. ❌ WRONG: Mixing color themes (indigo + blue)

  8. ✅ CORRECT: Pick one primary color (blue) and stick with it

  9. ❌ WRONG: Raw color classes everywhere

  10. ✅ 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.