---
name: design-system
description: StepLeague design system, theme configuration, CSS variables, badge styling, and UI patterns. Use when working with colors, themes, styling, badges, buttons, cards, or any visual UI changes. Keywords: CSS, theme, colors, dark mode, light mode, variables, styling, UI, design.
compatibility: Antigravity, Claude Code, Cursor
metadata:
version: "1.1"
project: "stepleague"
---
# Design System Skill
## Core Rule
**NEVER use hardcoded Tailwind colors. ALWAYS use semantic CSS variables.**
```tsx
// ❌ WRONG
// ✅ CORRECT
```
---
## Full Documentation
**MUST READ:** [docs/THEME_SYSTEM.md](file:///docs/THEME_SYSTEM.md)
This skill is a quick reference. The full theme documentation contains:
- Complete CSS variable reference
- Light/dark mode implementation
- Accessibility requirements
- Common pitfalls and solutions
---
## Quick Reference: CSS Variables
### Backgrounds
| Variable | Usage |
|----------|-------|
| `bg-background` | Page background |
| `bg-card` | Card/container background |
| `bg-muted` | Muted/subtle background |
| `bg-primary` | Primary action background |
| `bg-secondary` | Secondary background |
### Text
| Variable | Usage |
|----------|-------|
| `text-foreground` | Primary text |
| `text-muted-foreground` | Secondary/muted text |
| `text-primary` | Emphasised text (brand color) |
### Status Colors
```tsx
// Use HSL syntax for status colors
className="text-[hsl(var(--success))]" // Green
className="text-[hsl(var(--warning))]" // Amber
className="text-[hsl(var(--info))]" // Blue
className="text-destructive" // Red
```
### With Opacity
```tsx
className="bg-primary/90" // 90% opacity
className="bg-[hsl(var(--success)/0.1)]" // 10% opacity
```
---
## Badge Components
### Two Distinct Systems
| Component | Import | Use Case |
|-----------|--------|----------|
| `Badge` | `@/components/ui/badge` | General UI (variant prop) |
| `SystemBadge` | `@/components/ui/SystemBadge` | Category-based (Kanban, Roadmap) |
### shadcn Badge
```tsx
import { Badge } from "@/components/ui/badge";
DefaultSecondaryErrorOutline
```
### SystemBadge
```tsx
import { SystemBadge } from "@/components/ui/SystemBadge";
```
### Central Badge Configuration
All badge colors are in `src/lib/badges.ts`:
```typescript
import { getBadgeClass, getBadgeConfig } from '@/lib/badges';
const statusClass = getBadgeClass('status', 'verified');
const badgeConfig = getBadgeConfig('type', 'bug');
```
---
## Critical Patterns
### Text Truncation
**CRITICAL:** `truncate` won't work without `min-w-0`!
```tsx
// ✅ CORRECT
// Prevent icon shrinking
// Enable truncation
Long text...
// ❌ WRONG - truncate won't work
Long text...
```
### Hover States
```tsx
className="hover:bg-secondary"
className="hover:border-primary/50"
className="hover:bg-[hsl(var(--success)/0.1)]"
```
### Focus States
```tsx
className="focus:outline-none focus:ring-1 focus:ring-primary"
```
---
## ⚠️ Light/Dark Mode (CRITICAL)
### The Rule
**Every color MUST work in BOTH light AND dark mode. No exceptions.**
### How It Works
| Property | Value |
|----------|-------|
| Framework | `next-themes` |
| Attribute | `data-theme="light"` (NOT class-based) |
| Default | Dark mode |
| Toggle | User preference via settings |
### CSS Variable Pattern
**ALWAYS define BOTH variants:**
```css
/* In globals.css */
:root {
/* Dark mode (default) */
--custom-color: 200 50% 50%;
}
[data-theme="light"] {
/* Light mode - MUST be defined! */
--custom-color: 200 60% 30%; /* Darker for light bg */
}
```
### Common Mistakes
```tsx
// ❌ WRONG - Hardcoded, breaks in one theme
// ❌ WRONG - Only works in dark mode
// ❌ WRONG - Custom inline color
// ✅ CORRECT - Uses semantic variables
// ✅ CORRECT - With opacity
```
### Testing Requirement
> **ALWAYS test in BOTH themes before considering work complete.**
> Toggle to light mode via user settings or browser dev tools.
---
## ⚠️ Contrast Requirements (CRITICAL)
### WCAG 2.1 AA Standard
| Text Type | Minimum Ratio |
|-----------|---------------|
| Body text | **4.5:1** |
| Large text (18px+ or 14px bold) | **3:1** |
| UI components | **3:1** |
### How to Check
1. Use browser DevTools color picker
2. Or use [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/)
### Common Problem: Muted Text
```tsx
// ❌ RISKY - muted-foreground may have low contrast
Small muted text
// ✅ SAFER - use for secondary info only, not critical content
Secondary description
Important content here
```
### Status Colors Must Have Contrast
```tsx
// ✅ Status on dark background - use appropriate contrast
VerifiedError occurred
// ✅ Status as background - ensure text contrast
Verified // Uses pre-defined contrast
```
---
## Hardcoded Colors = FORBIDDEN
### Zero Tolerance Policy
| Forbidden | Why |
|-----------|-----|
| `bg-slate-*` | Breaks light mode |
| `text-gray-*` | Inconsistent theming |
| `border-zinc-*` | Not semantic |
| `#1e293b`, `rgb(...)` | Hardcoded |
| `dark:` prefix | Use `data-theme` instead |
### Allowed Patterns
| Pattern | Example |
|---------|---------|
| Semantic variables | `bg-card`, `text-foreground` |
| Status colors (HSL) | `text-[hsl(var(--success))]` |
| With opacity | `bg-primary/90` |
| Custom vars in globals.css | After defining light/dark variants |
### Finding Violations
Search for hardcoded colors in codebase:
```bash
# Find Tailwind color classes (potential violations)
grep -r "bg-slate\|bg-gray\|text-slate\|text-gray" src/
grep -r "border-zinc\|bg-zinc" src/
```
---
## Accessibility
### Screen Reader Support
```tsx
// Always add aria-label for icon-only buttons
```
### Focus Indicators
All interactive elements must have visible focus:
```tsx
className="focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
```
---
## shadcn/ui Components
### Installed Components
| Component | Location |
|-----------|----------|
| `toast`, `toaster` | Notifications |
| `dialog` | Modals |
| `dropdown-menu` | Dropdowns |
| `input`, `label`, `textarea` | Form fields |
| `select`, `checkbox` | Form controls |
| `tooltip` | Tooltips |
| `confirm-dialog` | Confirmation prompts |
### Usage
```tsx
// Toasts
import { toast } from "@/hooks/use-toast";
toast({ title: "Success!", description: "Saved" });
toast({ title: "Error", variant: "destructive" });
// Confirmation
import { ConfirmDialog } from "@/components/ui/confirm-dialog";
```
---
## Reference Files
| File | Purpose |
|------|---------|
| `docs/THEME_SYSTEM.md` | **Full documentation** |
| `src/app/globals.css` | CSS variable definitions |
| `src/lib/badges.ts` | Badge color configuration |
| `/admin/design-system` | Live component examples |
---
## Checklist
Before completing any UI work:
- [ ] Using semantic CSS variables (not hardcoded colors)
- [ ] Tested in BOTH light and dark themes
- [ ] Used `min-w-0` for flex containers needing truncation
- [ ] Used `flex-shrink-0` on icons in flex layouts
- [ ] Added focus indicators to interactive elements
- [ ] Text contrast meets 4.5:1 ratio
- [ ] Updated design system page if adding new patterns
---
## Related Skills
- `form-components` - Form field styling
- `architecture-philosophy` - Use shadcn over custom components