Engineering / theme-colors
Audit and refactor UI code so every color value comes from the theme.
Installation
Signierte, abgestufte Provenienz für diesen Skill. Integrität, Urheberschaft, Capability und Aktualität als Beleg, den du abwägst, nicht als Verifiziert-Badge.
Every color comes from the theme. No hex literals, no rgba() strings, no scattered values.
A theme defines colors by role (primary, secondary, error, surface, text) once, in one place. Components reference the role, not the value. Brand changes update one file. Dark mode is the same theme with different values. Components derive their own hover, focus, and pressed transparencies from the role color via the alpha() helper.
The most common failure mode in LLM-generated UI is hex codes sprinkled through component files (color: "#3B82F6", bgcolor: "#1E293B", background: "rgba(255,255,255,0.5)"). Training corpora are dominated by tutorial code that hardcodes colors. The defaults skew wrong.
| Role | Meaning | Used for |
|---|---|---|
primary | Main brand color | Main buttons, active tabs, key highlights |
secondary | Complementary accent | Switches, FABs, selection controls |
error | Something wrong or destructive | Error messages, delete buttons |
warning | Caution, not yet an error | Warning banners, risky-action confirmations |
info | Neutral information, no urgency | Tooltips, info badges, help text |
success | Positive outcome | Success notifications, completion |
background.default | Page background ("the desk") | The app's base background |
background.paper | Elevated surface ("paper on the desk") | Cards, dialogs, dropdowns, menus, drawers |
text.primary | Main text, high emphasis | Headings, body text |
text.secondary | De-emphasized text | Captions, timestamps, helper text |
Each role auto-derives .light, .dark, and .contrastText. Use the defaults; override only with a specific contrast or design reason.
Do this:
tsx<Box sx={{ color: "text.primary", bgcolor: "background.paper" }}><Button color="primary">Save</Button></Box>
Not this:
tsx<Box sx={{ color: "#1E293B", bgcolor: "#FFFFFF" }}><Button sx={{ bgcolor: "#3B82F6" }}>Save</Button></Box>
For transparency, use alpha() against a theme color, never a literal rgba():
tsximport { alpha } from "@mui/material";sx={(theme) => ({bgcolor: alpha(theme.palette.secondary.main, 0.08),})}
For gradients and any other string-typed CSS that needs theme values, use the sx callback form so the theme is available:
tsxsx={(theme) => ({background: `linear-gradient(135deg, ${theme.palette.primary.main} 0%, ${theme.palette.accent.alt} 100%)`,})}
createTheme({ palette: { primary: { main: "#6366F1" } } })). The theme file is where hex values live, on purpose.Anything else gets a theme token.
shgrep -rEn '(#[0-9a-fA-F]{3,8}|rgba?\(|hsla?\(|oklch\()' src/ app/ components/
rgba(R, G, B, A), identify the role the RGB approximates and replace with alpha(theme.palette.<role>.<variant>, A). Use the sx callback when needed.sx={(theme) => ...} callback and interpolate theme.palette.<role>.<variant>.color: "blue", bg: "white"), replace with the closest theme role.alpha(), not in the palette definition. A pre-diluted theme color stacks with hover/focus/pressed overlays and looks wrong..light / .dark / .contrastText until you have a concrete contrast or design issue. The defaults are derived with perceptual contrast.Based on Why Developers Keep Asking for Primary Instead of Blue.