.claude/skills/frontend-ui-dark-ts/SKILL.md
Builds dark-themed TypeScript UIs with accessible color systems, contrast compliance, and responsive design patterns. Use when implementing dark mode or building accessible TypeScript UI components.
npx skillsauth add tranhieutt/software_development_department frontend-ui-dark-tsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
prefers-color-scheme media query alone — users need a toggle; sync with localStorage to avoid flash on hydrationhsl(220 15% 10%) not #1a1a2e — HSL lets you programmatically adjust lightness#000) for dark backgrounds — causes eye strain; use hsl(220 15% 8%) insteadcolor-scheme: dark on :root makes browser UI (scrollbars, inputs) follow dark theme/* globals.css */
:root {
/* HSL values only (no hsl() wrapper) — allows opacity modifiers */
--bg-base: 222 47% 8%;
--bg-surface: 222 47% 12%;
--bg-elevated: 222 47% 16%;
--text-primary: 220 20% 95%;
--text-secondary: 220 15% 70%;
--text-muted: 220 10% 50%;
--brand: 220 90% 60%;
--brand-hover: 220 90% 65%;
--border: 220 20% 20%;
--error: 0 85% 60%;
--success: 142 70% 45%;
color-scheme: dark;
}
/* Light mode override */
[data-theme="light"] {
--bg-base: 0 0% 100%;
--bg-surface: 220 14% 96%;
--bg-elevated: 0 0% 100%;
--text-primary: 222 47% 11%;
--text-secondary: 220 14% 40%;
color-scheme: light;
}
// providers/ThemeProvider.tsx
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<"dark" | "light">(() =>
typeof window !== "undefined"
? (localStorage.getItem("theme") as "dark" | "light") ?? "dark"
: "dark"
);
useEffect(() => {
document.documentElement.dataset.theme = theme;
localStorage.setItem("theme", theme);
}, [theme]);
return (
<ThemeContext.Provider value={{ theme, toggle: () => setTheme(t => t === "dark" ? "light" : "dark") }}>
{children}
</ThemeContext.Provider>
);
}
// Prevent flash — add to <head> before React hydrates
const themeScript = `
(function() {
var t = localStorage.getItem('theme') || 'dark';
document.documentElement.dataset.theme = t;
})();
`;
// Button with all a11y attributes
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: "primary" | "ghost" | "danger";
isLoading?: boolean;
}
export function Button({ variant = "primary", isLoading, children, disabled, ...props }: ButtonProps) {
return (
<button
{...props}
disabled={disabled || isLoading}
aria-busy={isLoading}
aria-disabled={disabled || isLoading}
className={cn(
"inline-flex items-center gap-2 rounded-md px-4 py-2 font-medium transition-colors",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--brand))]",
"disabled:pointer-events-none disabled:opacity-50",
variant === "primary" && "bg-[hsl(var(--brand))] text-white hover:bg-[hsl(var(--brand-hover))]",
variant === "ghost" && "hover:bg-[hsl(var(--bg-surface))]",
variant === "danger" && "bg-[hsl(var(--error))] text-white",
)}
>
{isLoading && <Spinner aria-hidden="true" />}
{children}
</button>
);
}
// Use CSS variables with alpha
function token(variable: string, alpha?: number): string {
return alpha !== undefined
? `hsl(var(--${variable}) / ${alpha})`
: `hsl(var(--${variable}))`;
}
// Usage: token("brand", 0.2) → "hsl(var(--brand) / 0.2)"
// tailwind.config.ts
export default {
darkMode: ["class", '[data-theme="dark"]'], // class-based, controlled by JS
theme: {
extend: {
colors: {
bg: {
base: "hsl(var(--bg-base) / <alpha-value>)",
surface: "hsl(var(--bg-surface) / <alpha-value>)",
elevated: "hsl(var(--bg-elevated) / <alpha-value>)",
},
text: {
primary: "hsl(var(--text-primary) / <alpha-value>)",
secondary: "hsl(var(--text-secondary) / <alpha-value>)",
},
brand: "hsl(var(--brand) / <alpha-value>)",
}
}
}
}
// Quick WCAG contrast ratio check
function getContrastRatio(fg: string, bg: string): number {
// parse HSL → luminance → ratio
// Use online tool: https://webaim.org/resources/contrastchecker/
// Or: colord(fg).contrast(colord(bg))
}
// Minimum ratios:
// 4.5:1 → AA normal text
// 3.0:1 → AA large text (18pt+ or 14pt bold), UI components
// 7.0:1 → AAA normal text
| Pitfall | Fix |
|---|---|
| Flash of wrong theme on page load | Add inline script to <head> before hydration |
| Using opacity for text variants | Use separate CSS token with correct contrast ratio |
| Dark text (gray-900) on dark bg | Always test contrast; use --text-primary token |
| Hover states not visible in dark mode | Ensure hover has ≥3:1 contrast vs default state |
| currentColor for icons | Verify icon color passes 3:1 contrast vs background |
testing
Generates high-fidelity architecture diagrams, sequence flows, and component maps for SDD projects. Use when finalizing a design phase, documenting system architecture, or visualizing agentic workflows. Default style: Style 6 (Claude Official).
data-ai
Provides vector database and semantic search patterns for Pinecone, Weaviate, Qdrant, Milvus, and pgvector in RAG and recommendation systems. Use when implementing vector search or when the user mentions vector database, semantic search, embeddings, or similarity search.
development
Updates docs/technical/CODEMAP.md by scanning the current codebase structure. Run after a significant feature merge, refactor, or when CODEMAP feels stale.
development
Unlocks the codebase after a release freeze or incident freeze period to resume normal development. Use when a freeze period ends or when the user mentions unfreezing or lifting the code freeze.