




Before / after ·
Quick start ·
Why ·
Why not just prompt it? ·
What is verified ·
Features ·
Palette generator ·
Research ·
Contributing
What this is
Tastemaker is a skill for coding agents. Native support: Claude Code (recommended), Windsurf, and Gemini CLI. You install it once and forget it. Whenever you ask your agent to build or style a UI, tastemaker steps in and gives it a real design system to work from, instead of the generic defaults every model reaches for.
It is plain Markdown and small Python scripts. Everything runs on your machine. There is no hosted backend, no account, and no API key. Design memory is local too: project choices live in .tastemaker/style-lock.md and .tastemaker/decisions.log; durable personal preferences live in ~/.tastemaker/profile.md.
See the difference, not just the claim
One prompt, build a landing page for a coffee subscription, built twice: once with no skill, once with tastemaker installed. Same request, unchanged, both times.
Open the live before/after →
Left is the indigo-gradient, letter-in-a-box-logo, emoji-icon default most agents reach for. Right is the same prompt with tastemaker: a palette generated fresh for the project's mood, real fetched icons, a constructed mark, and motion, in one pass. Both are real, live pages, not mockups or screenshots.
Why AI UI all looks the same
Ask any model to build a UI and you tend to get the same thing: an indigo to purple gradient, a soft shadow card, a generic hero. This is not a prompting problem. It happens because the model has to invent taste from a text description, with nothing real to ground it and no memory of what you actually like.
Tastemaker fixes this with four ideas, not a bigger catalog of canned options to pick from:
- Generate within a check that actually runs. There is no fixed list of color combinations shipped with this skill, and the palette is not five approved colors the model may combine however it likes either. A new one is generated per project (a fresh hue and harmony each time) against a contract:
check_contrast.py --matrix computes every pairing and says which may carry text, which may carry a border, and which may carry neither. So the constraint produces variety instead of sameness, and a rule that runs is different in kind from a rule you wrote down, because it returns the same answer no matter how confident anyone felt.
- Ground in real pixels, not words. Give it a screenshot or a reference and it reads the real colors and contrast from the actual image, using a script. It does not write a vague summary of the vibe and rebuild from that. Text summaries lose most of what made the reference feel specific.
- Remember, do not re-derive. Once a project locks a style, every later screen reuses it. Nothing drifts. Across projects, a small profile file learns what you keep and what you reject, so your next project starts warm.
- Scope to the real work. It reads your spec first and figures out which screens actually need design, instead of dumping a design system that has nothing to do with what you are shipping.
"Can I not just tell the AI to write the decisions down?"
Yes, partly, and it is worth being straight about where the line is.
If you say "lock these decisions as a design bible and use it as our anchor," you get the decisions written down in the current context. For keeping three screens consistent inside one chat, that genuinely works, and you do not need this skill for it.
Here is what that does not give you:
- It does not survive the session. The bible lives in context. Close the chat and it is gone, or you re-paste it and hope. Tastemaker writes
.tastemaker/style-lock.md and .tastemaker/decisions.log to your repo, then promotes durable resolved preferences into ~/.tastemaker/profile.md. Project decisions survive the conversation, and real keep/reject patterns can carry into the next project.
- It has no check that runs. A written-down preference is still a judgment you can talk yourself out of.
check_contrast.py --matrix is a computation. It does not care how good the palette looked to you, and it returns the same verdict every time. That is the difference between an intention and a constraint.
- It cannot read pixels. "Match this reference" through a conversation becomes a text description of an image, then a rebuild from the description.
extract_palette.py reads the actual pixel values.
- It leaves the combinations to improvisation. A written bible lists your colors. It does not enumerate which of those colors may legally touch which, so the model still guesses when it invents a badge fill or a disabled state. The matrix answers that up front.
Short version: a conversation gives you the decision. This gives you the decision plus something that enforces it after you have stopped paying attention.
What is verified, and what is judgment
Worth separating these two clearly, because it is easy to let one stand in for the other, and this project has been guilty of that.
Verified (a computation, not taste). Contrast and readability. check_contrast.py runs real WCAG math over the palette and reports pass or fail. This is accessibility, not aesthetics. A palette that clears every ratio can still be ugly. The reason it belongs here anyway is that it catches a class of failure your eyes genuinely cannot: contrast is a calculation, and looking at a color confidently is not running it. Early hand-picked palette drafts for two moods failed that check on the first pass, and only the script caught it. That failure is the reason color is generated against the contract now instead of hand-tuned and hoped.
Judgment (heuristics and memory, not proof). Everything that is actually taste: the reference extraction, the mood-to-palette matching, the accumulated profile, and the anti-slop checklist. These are informed defaults and accumulated preference. They are not verified, and this README should not imply they are. They get better with your references and your rejections, not with more math.
Do not read the contrast script as evidence that the design is good. Read it as evidence that the design is legible, which is a smaller and more checkable claim.
Quick start
Claude Code plugin (recommended):
/plugin marketplace add codeswithroh/tastemaker
/plugin install tastemaker@codeswithroh
Manual install, for Claude Code without the plugin system, or for Windsurf: clone the repo, then copy or symlink the skills/tastemaker folder specifically (not the whole repo) into your tool's skills directory:
git clone https://github.com/codeswithroh/tastemaker /tmp/tastemaker
cp -r /tmp/tastemaker/skills/tastemaker ~/.claude/skills/tastemaker
Using Windsurf? Same copy, into .windsurf/skills/ instead. Windsurf reads the native SKILL.md format directly, references/ and all.
Gemini CLI: one command, no manual copying. Gemini CLI reads the native SKILL.md format directly too, references/, scripts/, and the vendored ideagram/ sub-skill included, verified with a real local install, not assumed from the file-format similarity to Claude Code:
gemini skills install https://github.com/codeswithroh/tastemaker --path skills/tastemaker
This installs tastemaker (and ideagram, its vendored illustration sub-skill) globally for Gemini CLI, byte-for-byte identical to the source. First run in a project may ask you to trust the folder before workspace-scoped skills activate; global installs (the default here) don't need that.
Cursor: not a straight copy, and currently degraded even then. Cursor doesn't read SKILL.md natively; it needs conversion to its own .mdc rule format (via a tool like openskills), and that conversion strips out supporting directories entirely. Since nearly all of tastemaker's actual mechanism lives in references/ (the palette contract, macrostructure catalog, anti-slop gates) and scripts/ (the palette generator, contrast checker), a Cursor install currently loses the parts that make tastemaker work, not just a cosmetic difference. Verified against Cursor's actual conversion behavior rather than assumed from the plugin-format similarity to Claude Code. Until Cursor ships native SKILL.md support with subdirectories intact, don't expect full functionality there.
Restart your agent, then just ask:
build a landing page for a coffee subscription
Tastemaker triggers on its own. It generates a palette, picks a matching type pairing, sources real assets, wires up motion, and builds. You do not invoke anything.
For the deterministic color extraction script you need Python 3 and Pillow (pip install Pillow). If Pillow is missing, it falls back to a vision based read instead of failing.
What you get
| |
|---|
| Grounded in real pixels | Reference images become real color tokens through scripts/extract_palette.py, not a text guess. |
| Reference-led without user references | Cold starts create .tastemaker/reference-board.md: competitors, adjacent products, cultural sources, interface systems, anti-references, dials, and a direction contract. |
| A new palette every time, never reused | generate_palette.py builds a fresh palette per project: a base hue in the mood's range, a color-harmony rule for the accent, and per-role lightness solved so the contrast pairings clear their floors. Two similar prompts get two different, legible palettes, never the same one twice. |
| A contrast contract, not a one time check | check_contrast.py --matrix computes every pairing in the palette and reports which may carry text, which may carry a border, and which may carry neither. The generator satisfies this by construction, so a fresh palette is still a legible one. This buys readability, not taste. |
| Mechanical anti-slop scan | anti_slop_scan.py flags high-confidence tells such as generic purple gradients, gradient text, h-screen, dead links, missing alt text, placeholder copy, emoji icons, eyebrow spam, and transition-all. |
| Real illustrations | Each concept is matched to real illustrator grade art and recolored to your palette, not drawn from scratch by the model. |
| A real logo, not a letter in a box | A constructed geometric mark plus a full favicon set, readable down to 16px. |