by codeswithroh
A Claude Code skill that grounds AI-generated UI in real reference images and a persistent per-developer taste profile, instead of generic AI-slop defaults.
# Add to your Claude Code skills
git clone https://github.com/codeswithroh/tastemakertastemaker is an open-source ai agents skill for AI coding assistants such as Claude Code, Codex CLI, and ChatGPT, built by codeswithroh. A Claude Code skill that grounds AI-generated UI in real reference images and a persistent per-developer taste profile, instead of generic AI-slop defaults. It has 52 GitHub stars.
tastemaker's catalog security scan is still queued. You can run an instant dependency and prompt-injection check now with the "Scan for vulnerabilities" button above.
Clone the repository with "git clone https://github.com/codeswithroh/tastemaker" and add it to your Claude Code skills directory (see the Installation section above). tastemaker ships a SKILL.md manifest, so compatible agents can discover and load it automatically.
tastemaker is primarily written in Python. It is open-source under codeswithroh on GitHub, so you can review or fork the full source.
Yes. SkillsLLM lists many other AI Agents skills you can browse and compare side by side. Open the AI Agents category from the badge at the top of this page, or use the Related Skills and comparison links further down to weigh tastemaker against similar tools.
No comments yet. Be the first to share your thoughts!
Unlocks once the catalog security scan passes (runs nightly).
The deep catalog scan for this skill is still queued. Run an instant dependency check now instead.
Ask an LLM to build a UI and it defaults to the same handful of patterns: indigo-to-purple gradients, the same rounded card with a soft shadow, the same generic hero layout. This isn't a prompting failure — it's what happens when a model has to invent visual taste from scratch, from a text description, with no grounding and no memory of what the person asking actually likes.
Most "design skill" approaches try to fix this by handing the model a bigger catalog of canned styles and palettes to pick from. That helps a little, but it's still generic — a library of canned options, not your taste, and it forgets everything the moment the session ends.
Tastemaker works differently, on three ideas:
Read this file top to bottom before starting. It's short by design; the reference files below hold the deep material and are only worth opening when the step calls for them.
Tastemaker has one default behavior and two explicit verbs. Detect which one the request is before starting — most requests are the default.
| Mode | When | What it does |
|---|---|---|
| build (default) | The user asks to design, build, style, or improve UI. | The Workflow below (Steps 0-5). |
| study | The user pastes a screenshot or URL of a design they admire and wants to learn from it — "study this," "what makes this work," "match this vibe." | Extract the reusable DNA (macrostructure, archetypes, type pairing, color anchor) — never the pixels — into a diagnosis, then optionally build the user's own content with it or lock it into the style lock. Load references/verbs/study.md first. |
| audit | The user points at existing UI and wants a critique, not a change — "audit this," "why does this look AI-generated," "review this page." | Score it against the numbered gate list in references/anti-slop-checklist.md, return a ranked punch list by severity. Does not edit. Load references/verbs/audit.md first. |
If a request doesn't clearly map to study or audit, it's the default build. If the user pastes a reference image or URL with no verb, ask once whether to study it (extract reusable DNA) or treat it as a reference to ground a fresh build (Step 2's extract-palette path). A follow-up like "now fix it" after an audit, or "build it" after a study, hands off into the build Workflow.
Check for .tastemaker/style-lock.md in the project root first.
.tastemaker/log.json if present (the structural build log, see references/diversification.md) — it records the macrostructure and archetype picks of previous builds so this build can rotate to a different shape instead of repeating one.~/.tastemaker/profile.md (outside the repo, in the user's home directory) for a personal taste profile accumulated across their other projects. If it exists, treat it as a strong prior — propose starting from it rather than starting neutral. If neither file exists, this is a genuinely cold start; go to Step 1.Before touching color or type, scope the work:
This only runs on a cold start (Step 0 found neither file), or when the user explicitly asks to change the project's direction.
references/style-tokens.md's Non-Latin script typography section before picking type. It's a different model (one family across a weight scale, not two families), not a font substitution inside the same one.scripts/extract_palette.py against the image(s) to get deterministic dominant colors, contrast ratios, and lightness stats — real numbers pulled from real pixels, not a guess. Combine that with your own visual read of the reference (layout density, corner radii, shadow depth, whether it leans playful/serious/technical) to write a concrete style brief. Anchor every token to something visible in the reference — if you can't point to why a color or pattern is in the brief, don't include it. Once you've assigned extracted colors to Primary/Accent roles, run scripts/check_contrast.py --palette ... on the assignment — a color that looked fine as a dominant swatch in the source image can still fail as a button-label background once it's assigned that role (see references/style-tokens.md's Contrast floor section for a concrete example of this exact failure).references/style-tokens.md, then run scripts/generate_palette.py --mood <mood> (add --mode light|dark if the product implies one). This produces a new, legible-by-construction palette every run, so two similar projects don't come out identical, which is the whole point: no monoculture. Pair it with the mood's font set from that file's type-pairing catalog. Only ask a direct question when the idea genuinely spans two moods with no lean, per that file's "When to actually ask" section. State in one line which mood was inferred and why. Use references/component-patterns.md for layout pattern choice the same way (adapt, don't apply unchanged). (The five hex palettes in style-tokens.md are reference anchors showing each mood's intended character, not palettes to ship.)references/style-tokens.md's Runtime dark/light toggle section: generate the companion pair from the same --seed in both --mode light and --mode dark, verify both with check_contrast.py --matrix, and record the toggle decision in the lock's Dark mode line per references/style-lock-format.md.generate_palette.py prints it, and for a reference-extracted palette you run scripts/check_contrast.py --matrix text=.. bg=.. surface=.. primary=.. accent=.. border=.. on-primary=.. yourself. Write the result — palette, type, and the legal-pairings summary from the matrix — to .tastemaker/style-lock.md's Color contract section (see references/style-lock-format.md for the exact structure). This is what makes the lock a contract over which colors may touch, not just a set of hexes that happened to pass once; every later step in this project reuses it instead of re-deriving.Color is now locked and varies per project. But two sites with different palettes still read as the same template if they share the same page shape — the generic hero → 3-feature-cards → testimonial → CTA → footer rhythm is the strongest "an AI built this" tell at the page level, and it survives a perfect palette. This step makes structure vary the way color already does. Skip it for App shell screens (dashboards/internal tools) — their shape is governed by references/component-patterns.md's App shell section; this step is for public/marketing pages, where structural sameness bites hardest.
.tastemaker/log.json (created by previous builds; see references/diversification.md) alongside the style lock. It records the last builds' macrostructure and archetype picks.references/macrostructures.md — the whole-page skeleton — matched to the brief's actual argument, and different from the last build's per the rotation rule.references/component-catalog.md — nav, hero, feature, proof, CTA, footer, section-head — each with its variation knobs. Nav, footer, and hero must each differ from the last build's; if you reuse an archetype, change a knob.references/diversification.md for the exact format..tastemaker/log.json entry (both written at Step 4) record. Within a single project, keep pages coherent (shared nav/footer/type frame); across projects, structure varies.The picks feed Step 3 (which assets each archetype needs) and Step 4 (the build itself).
A site with no real photography, no illustrations, and no motion reads as static and generic no matter how good the color/type tokens are — this step is what makes a generated site feel dynamic and alive. The goal is a complete site in a single pass: every section that needs a photo has a real photo, every concept has an illustration, every icon is in place, and it all animates — the first time, with no follow-up round of "now add the images." Every source below is chosen to make that possible: API-first (fetchable automatically, no human browsing step) and attribution-free (nothing the end user ever has to see). This is deliberate — an attribution credit sitting on a finished marketing site is a visual hindrance no real product ships, so this skill sources only from places that don't require one.
For every asset the scoped screens need:
Decide illustration vs. real photography per section. Sections showing something factual or physical (office, product-in-use, people, places) call for real photography; sections conveying an abstract concept (mission, values, an idea, a feature benefit) call for illustration. Both get filled in this same pass — neither is optional.
Real photography → Openverse, via scripts/fetch_photos.py, automatically and with no API key. Run scripts/fetch_photos.py "<search terms>" --out design/assets/photos for every photo-appropriate section — no key, no signup, nothing to set up. It searches Openverse (800M+ openly-licensed images), filtered by default to CC0 + Public-Domain-Mark, which legally require zero attribution — nothing on the site, ever. This is what makes photos and icons zero-setup: keyless in every session, no accounts, no keys. (Illustrations are the one asset type that may need a single one-time setup step — populating ~/.ideagram/undraw/ — see the Illustrations bullet below; once that's done once, it's reused across every future project.) (Optional upgrade: --source pixabay uses Pixabay for more stock-polished, full-res imagery if a section needs it — also attribution-free, but needs a free PIXABAY_API_KEY. Use it only when Openverse's more eclectic pool doesn't have a clean match. Unsplash is deliberately not used at all, because its API forces visible on-site attribution.)
Credit the sources in the code, never on the page. fetch_photos.py writes a CREDITS comment block (creator + source + license per photo) into the photos folder. This is a voluntary courtesy — CC0/PDM require nothing — so paste it into a code comment at the top of your HTML/CSS as a genuine thank-you to the people whose work you're using. It's visible to any developer reading the source and invisible to the end user. That's the honest middle ground: generous credit, zero visual hindrance. Never promote it to visible on-page text.
Logo → preserve first; construct only on a real cold start. See references/logo-sourcing.md. Before designing a mark, search the repository and brief for an existing logo, favicon, brand kit, or distribution asset. If one exists, treat it as locked: reuse it byte-for-byte unless the user explicitly requests a rebrand. Do not recolor, reinterpret, or replace an established mark merely because it clashes with the new page direction. Only when no identity exists should you construct a simple geometric mark from primitive shapes in the locked palette (following ideagram/references/style-contract.md) — not a letter dropped in a colored box. Once the mark is established, run scripts/export_favicons.py <mark>.svg --out design/assets/favicons/ and wire the favicon set into <head>.
Illustrations → the vendored ideagram/ skill, always available, no cross-skill dependency. ideagram/ is bundled directly inside this skill (see ideagram/SKILL.md) specifically so illustration sourcing never depends on whether a separate ideagram skill happens to be installed in the current session — it's part of tastemaker now, not an optional sibling. Two things trigger this step, and both use the exact same workflow:
The workflow itself (full detail in ideagram/SKILL.md, read it before the first use in a project): distill the concept to one sentence, match it against a local unDraw library at ~/.ideagram/undraw/ (real illustrator-grade SVGs, not hand-drawn-by-LLM path data), recolor the match to this project's locked accent with ideagram/scripts/recolor_undraw.py, validate with ideagram/scripts/validate_assets.py. If ~/.ideagram/undraw/ doesn't exist yet or has no index, say so plainly and either ask the user to grab 20-30 free illustrations from undraw.co (30 seconds, no attribution needed) or fall back to ideagram/assets/primitives composition — and be upfront that the fallback is a real quality drop from real unDraw art, per ideagram's own honesty rule. Don't silently accept the downgrade as if it were the intended result.
design/assets/illustrations/, then actually reference that file path in the section's markup (<img src="design/assets/illustrations/<name>.svg" alt="..."> or inlined <svg>) as part of the same pass — generating an on-brand illustration and leaving it unused on disk isn't a completed step..tastemaker/style-lock.md's Assets section (illustration vs. photography split line) — including whether the library was populated or the primitive fallback was used, so a later session in this project doesn't have to rediscover that state.Icons → Iconify, via scripts/fetch_icons.py, automatically, matched to the mood. Iconify's public API needs no key, returns SVGs already tinted to the accent color, and draws from permissively-licensed open sets (Lucide, Tabler, Phosphor, Heroicons, Material Symbols, Iconoir, Solar, Carbon, MingCute, Fluent) that require no attribution. Pick one set per project and stay in it so every icon shares one stroke weight — but which set is the point that used to get skipped: pass --mood <the project's locked mood> so the set varies with the project, and each mood maps to two candidate sets rather than one, picked between per run the same way generate_palette.py --seed varies the palette (the mapping lives in the script's docstring) — so two "technical" projects aren't guaranteed the same Lucide icons any more than two "premium" ones are guaranteed the same palette. scripts/fetch_icons.py --search "<terms>" --mood <mood> to discover names, then --icons name1 name2 --mood <mood> --color "#<accent>" --out design/assets/icons to fetch. Name --set directly only when neither of the mood's candidates fits this project. Don't fall back to emoji-as-icons or hand-drawn one-offs when a two-line fetch gets a consistent, real icon set.
When even a populated unDraw library has no real fit for a concept (per ideagram/SKILL.md Step 2's "don't force a bad match" rule), or an image-gen tool happens to be available for a bespoke style: references/illustration-sources.md covers the remaining options, including Streamline as a manual, attribution-aware exception. This is now the rarer path — ideagram's unDraw-match workflow (once the library is populated) is the norm, not Openverse/Iconify-style full automation, because the win here is real illustrator-grade art, not a zero-touch fetch.
Last-resort fallback, never the plan: if a specific asset genuinely can't be sourced (no network, no populated unDraw library and the user can't add one), build it code-native — SVG shapes / CSS gradients from the locked palette — rather than leaving a gap or a grey box. Say plainly when this happened; don't imply a real photo/illustration exists where a placeholder does. Note this should be rare for photos and icons specifically, since both fetch without any key — the illustration path is the one place a one-time manual step (populating ~/.ideagram/undraw/) may genuinely still be needed.
Save everything into design/assets/ (photos, illustrations, icons in their own subfolders) so it's reusable across screens, and run scripts/validate_assets.py over any SVGs before use — a malformed SVG (classically a -- inside a <!-- --> comment) reads fine as text but renders as a broken image in strict browsers, invisible unless actually parsed.
Motion → GSAP + ScrollTrigger, in the same pass, not a later polish step. Wire up assets/gsap-starter.js for scroll-driven reveals and staggered entrances, and for anything with a narrative/storytelling shape (a landing page that unfolds section by section) build a GSAP scroll timeline per references/animation-guidelines.md — pinned sections, scrubbed reveals, sequenced hero moments. This is what turns a stack of static sections into a site that tells a story as you scroll, and it's default, not optional. The dependency-free reveal.css/reveal.js pair remains only as a fallback for contexts that can't take a GSAP dependency.
Now generate the PRD-scoped screens/components, constrained to .tastemaker/style-lock.md and the asset files from Step 3. Point explicitly at file paths and token values rather than re-describing the vibe in prose each time — concrete constraints produce consistent output; restated vibes drift.
Six defaults are non-negotiable at build time — they are the difference between "a styled document" and "a designed product," and generated sites reliably skip them unless forced:
references/component-patterns.md for the show-don't-tell pattern per section type. This is a hard default, verified in the anti-slop checklist, not a stylistic preference.references/hero-guidelines.md before building above the fold. Start from one sharp promise, one short explanation, one primary action (plus at most one secondary action), and one product-relevant visual. Do not turn the hero into a miniature dashboard of the site's features: workflow rails, metric sidebars, floating badges, proof stamps, orbit decorations, file/status footers, and extra microcopy compete with the value proposition. Push explanation and process below the fold. Complexity is allowed inside the product; it is not automatically allowed in the product's first impression.references/animation-guidelines.md:
assets/gsap-starter.js for baseline reveals and build a real sequenced hero timeline plus at least one scroll-storytelling beat (scrubbed reveal, pinned section, parallax)..tastemaker/style-lock.md says which pairings are text-safe (>=4.5:1) and which are UI-safe (>=3.0:1). When a screen needs a pairing not already in that list (a badge fill with a label, a disabled state, a hover, a state-carrying border), that is a flag, not a free choice: pick a pairing that's already legal for the purpose, or run scripts/check_contrast.py --matrix again with the new token added and update the lock before shipping it. Don't invent a pairing and assume it's fine because the palette passed once — early hand-picked drafts for two moods in references/style-tokens.md failed exactly this way, caught only by actually running the numbers, which is why color generates against this contract now (scripts/generate_palette.py) instead of being hand-tuned and hoped.
check_contrast.py --matrix on the result, and only accept it once the matrix confirms the pairing actually clears its floor — this is the same lightness-solving approach scripts/generate_palette.py already uses at generation time, applied ad hoc to a single new token.text or on-primary) for that specific pairing instead of forcing the original color to work.references/style-tokens.md's Spacing scale section: pick the project's tokens once, record them in .tastemaker/style-lock.md's Density & spacing section, and reuse them — don't let each card or section improvise its own padding. The rule that actually governs the result: internal spacing (a card's own padding) should be equal to or less than external spacing (the gap between that card and its neighbors) — violating this is what makes a layout read as cramped in one place and empty in another at the same time, a real failure mode this rule was written to catch. Content cards (pricing tiers, feature cards, testimonials) have a real floor: space-6 (24px) minimum internal padding, not whatever a compact stat tile uses. Don't reach for the same padding value everywhere regardless of what the element is.Stamp the build and record it in project memory. The first non-empty line of the built CSS (or the top of an inline <style>) is a comment recording the structural picks, mood, palette seed, and contrast result — the format is in references/diversification.md. In the same pass, append an entry to .tastemaker/log.json (create it if absent) with the macrostructure and archetype picks. This is the durable record the next build reads to rotate against — skipping it is how the skill drifts back into building the same shape every time.
references/anti-slop-checklist.md carries two checks that bracket the build. Before you finalize, run its pre-emit self-critique: score the planned output 1-5 on six axes (show-don't-tell, philosophy, hierarchy, specificity, restraint, variety) and revise anything scoring below 3 — catching weakness there is cheaper than catching it in the gate sweep. After you build, run its numbered gate list (mood-scoped: some gates loosen or tighten per the project's mood) — it catches the specific tells (generic gradient defaults, emoji-as-icons, contrast checked on only one pairing, text-walls where visuals belong, static/no-motion pages, transition: all and other motion tells, mid-render token improvisation, the generic hero→3-cards→CTA→footer template, invented metrics) that make output read as AI-generated regardless of how good the underlying tokens were. Record the six critique scores in the build stamp.
Taste lives in what gets kept vs. rejected, not in the first draft. What this looks like depends on whether you can actually get a response back:
.tastemaker/decisions.log.status: pending-review with what was shown and why you chose it, so a later session (or the human, whenever they do look) has the reasoning available and can convert it to a real kept/rejected verdict then. A pending entry is honest; an invented "user approved this" is not.~/.tastemaker/profile.md — the kind of preference that would matter on a different project too (e.g. "prefers muted/desaturated palettes over saturated ones," "consistently rejects skeuomorphic shadows"), not project-specific detail that belongs only in that project's lock file. Don't promote pending/unresolved entries into the global profile — only real revealed preference should shape future cold starts.This is what makes the second project faster than the first, and the tenth faster than the second — the tool is meant to get more accurate for a given developer the more they use it, not stay static.
| File | Read when |
|---|---|
references/style-lock-format.md |
Writing or updating .tastemaker/style-lock.md |
references/style-tokens.md |
Cold start with no references — auto-selects a matched palette + Google-Font pairing from the app idea's mood, plus spacing/radius/shadow scales |
references/macrostructures.md |
Step 2.5 — picking the whole-page shape for a public/marketing page (Feature Stack, Editorial Index, Long-Scroll Narrative, Bento Showcase, …) so structure varies per project, not just color |
references/component-catalog.md |
Step 2.5 — the named component archetypes (nav/hero/feature/proof/CTA/footer/section-head) with stable IDs + variation knobs that fill a macrostructure |
references/diversification.md |
Step 2.5 / Step 4 — the project-memory engine (.tastemaker/log.json), the rotation rule that forces each build to differ structurally from the last, and the build-stamp format |
references/verbs/study.md |
The study verb — extracting reusable design DNA from a screenshot or URL (never pixels), then building from it or locking it |
references/verbs/audit.md |
The audit verb — scoring existing UI against the numbered gate list and returning a ranked punch list, without editing |
references/component-patterns.md |
Choosing a layout pattern for a given screen type (landing, app shell/dashboard, pricing, onboarding, empty states) |
references/hero-guidelines.md |
Building or revising any landing-page hero — attention budget, copy limits, single-focus visual, responsive and motion checks |
references/logo-sourcing.md |
Step 3 — preserving an existing identity, or designing a real cold-start mark (no letter-in-a-box) + wordmark and favicons |
references/anti-slop-checklist.md |
The pre-emit self-critique (before finalizing, Step 4) + the numbered, mood-scoped gate sweep (after building) — the two quality checks that bracket every build |
references/tech-stack-guides.md |
Implementing tokens/components in a specific stack (React/Next/Tailwind, Vue, SwiftUI, Flutter) |
references/animation-guidelines.md |
Adding motion (Step 3/4) — GSAP + ScrollTrigger is the default engine, including scroll-storytelling timelines; read this first |
references/illustration-sources.md |
The attribution-free asset sourcing map (Step 3) — Openverse for photos, the vendored ideagram/ for illustrations, Iconify for icons, Streamline as a manual exception; plus the "credit in code, never on the page" pattern |
ideagram/SKILL.md |
Step 3, Illustrations — the vendored illustration workflow itself (unDraw-library match → recolor → validate). Read before the first illustration in a project. Triggered implicitly by a concept-driven section, or explicitly whenever the user says "illustration"/"illustrate." |
| Script | Purpose |
|---|---|
scripts/generate_palette.py |
Generate a fresh, contrast-valid palette for a mood (OKLCH + color harmony, per-role lightness solved against the contract). The default cold-start path, so output isn't one of five fixed options. Usage: python3 scripts/generate_palette.py --mood <premium|warm|technical|playful|elegant> [--mode light|dark] [--seed N]. Prints the roles, a preview URL, and the contrast matrix to record in the lock. |
scripts/extract_palette.py |
Deterministic color/contrast extraction from reference image(s). Usage: python3 scripts/extract_palette.py <image_path> [image_path ...] |
scripts/check_contrast.py |
WCAG contrast check for a palette. --palette text=hex bg=hex primary=hex accent=hex checks the critical few pairings; check_contrast.py <hex1> <hex2> checks a single pair. --matrix text=hex bg=hex surface=hex primary=hex accent=hex border=hex on-primary=hex computes every pairing in the token set and reports which are text-safe (>=4.5:1), UI-safe (>=3.0:1), or decorative — this is what Step 2 records as the Color contract in .tastemaker/style-lock.md, and what Step 4 checks new pairings against (see references/style-lock-format.md). Run this on any palette, the reference anchors, an extracted reference-image palette, or a user-supplied brand color, not just the starter ones. |
scripts/validate_assets.py |
Validate SVG assets are well-formed before shipping them (Step 3/4). Usage: python3 scripts/validate_assets.py <file_or_directory> |
scripts/fetch_photos.py |
Fetch real photography from Openverse — no API key, CC0/public-domain (attribution-free), writes a voluntary code-comment CREDITS block. Usage: python3 scripts/fetch_photos.py "<query>" --out design/assets/photos. Optional --source pixabay (needs PIXABAY_API_KEY) for higher-curation imagery. |
scripts/fetch_icons.py |
Fetch icons from Iconify — no API key, attribution-free, pre-tinted to the accent, set chosen by mood so projects don't all get the same icons. Usage: python3 scripts/fetch_icons.py --search "<terms>" --mood <mood> then --icons a b c --mood <mood> --color "#hex" --out design/assets/icons (or --set <prefix> to name one directly) |
scripts/recolor_svg.py |
Recolor local SVG files (already on disk) to match the locked accent color. Usage: python3 scripts/recolor_svg.py <path> --accent "#hex" --preserve-dark |
scripts/export_favicons.py |
Export a logo mark SVG to favicon.ico, apple-touch-icon, PWA manifest icons, and an OG-card PNG (needs cairosvg + system cairo, same as ideagram/scripts/export_png.py). Usage: python3 scripts/export_favicons.py <mark>.svg --out design/assets/favicons/ |
ideagram/scripts/build_library_index.py |
Index a local unDraw library (~/.ideagram/undraw/) into index.md/index.json for matching. Run once after populating/updating the library. |
ideagram/scripts/recolor_undraw.py |
Recolor a matched unDraw illustration's accent to the project's locked accent, preserving skin/ink/clothing/neutrals. The illustration workhorse — see ideagram/SKILL.md Step 3. |
ideagram/scripts/extract_component.py |
Lift a whole figure/device/panel out of a source unDraw SVG to compose a custom scene, for the rare case no single library illustration fits (ideagram/SKILL.md Step 4). |
ideagram/scripts/export_png.py |
Export a finished illustration to social/presentation PNG sizes (needs cairosvg + system cairo). |
| File | Use when |
|---|---|
assets/gsap-starter.js |
Default motion for every project. Wires the data-reveal/data-reveal-group markup convention to GSAP + ScrollTrigger — reduced-motion-aware via gsap.matchMedia(). Requires GSAP/ScrollTrigger loaded first (see references/tech-stack-guides.md for CDN vs. npm per stack). |
assets/reveal.css + assets/reveal.js |
Fallback only, for contexts that can't take a GSAP dependency — same markup convention as gsap-starter.js, so switching between them requires no markup changes. |
Don't claim a step happened if it didn't. If there was no image-generation tool available and you fell back to curated icons + code-native visuals, say so plainly rather than implying custom illustrations were generated. If no references were given and the style came from the starter scaffolding, say that too. The whole point of this skill is to close the gap between "looks AI-generated" and "looks intentional" — silently overclaiming what happened undermines the exact trust it's trying to build.
Tastemaker is a skill for coding agents (Claude Code, Cursor, Windsurf). 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.
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.
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.
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:
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.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:
.tastemaker/style-lock.md to your repo and ~/.tastemaker/profile.md to your home directory, so the decision outlives the conversation and carries into the next project.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.extract_palette.py reads the actual pixel values.Short version: a conversation gives you the decision. This gives you the decision plus something that enforces it after you have stopped paying attention.
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.
Install it into your Claude Code skills folder:
git clone https://github.com/codeswithroh/tastemaker ~/.claude/skills/tastemaker
Restart Claude Code, 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.
Using Cursor or Windsurf? Drop the same folder into their skills directory.
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.
| Grounded in real pixels | Reference images become real color tokens through scripts/extract_palette.py, not a text guess. |
| 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. |
| 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. |
| Motion by default | GSAP and ScrollTrigger reveals plus a sequenced hero, wired during the build and not left as a follow up. |
| Attribution free assets | Photos (Openverse), icons (Iconify), and illustrations all need no keys and no visible credit line. |
| Taste that compounds | A local profile remembers what you keep across projects, so the tool gets more accurate the more you use it. |
When you have no reference, tastemaker does not hand you a color picker, and it does not hand you a fixed color scheme to choose from either. It generates one, on the spot, for your project. A menu of