<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: yang7206</title>
    <description>The latest articles on DEV Community by yang7206 (@yang7206).</description>
    <link>https://dev.to/yang7206</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4200626%2F96519eb9-8bdc-441c-a681-eb581bb150d2.png</url>
      <title>DEV Community: yang7206</title>
      <link>https://dev.to/yang7206</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yang7206"/>
    <language>en</language>
    <item>
      <title>One brand color, three platforms: what actually changes</title>
      <dc:creator>yang7206</dc:creator>
      <pubDate>Sat, 31 Oct 2026 13:48:57 +0000</pubDate>
      <link>https://dev.to/yang7206/one-brand-color-three-platforms-what-actually-changes-4720</link>
      <guid>https://dev.to/yang7206/one-brand-color-three-platforms-what-actually-changes-4720</guid>
      <description>&lt;p&gt;A while back I inherited a design system that had been ported from web to a mini program. The primary color made it. Almost nothing else did.&lt;/p&gt;

&lt;p&gt;The tokens were all there. &lt;code&gt;primaryHover&lt;/code&gt;, &lt;code&gt;primaryActive&lt;/code&gt;, &lt;code&gt;border&lt;/code&gt;, &lt;code&gt;surface&lt;/code&gt;, &lt;code&gt;textSub&lt;/code&gt;. Someone had copied them into the mini program stylesheet in good faith. Then, one at a time, they became dead weight. &lt;code&gt;border&lt;/code&gt; had no consumer, because the mini program draws dividers instead. &lt;code&gt;primaryHover&lt;/code&gt; sat there doing nothing, because a finger cannot hover.&lt;/p&gt;

&lt;p&gt;The usual explanation is "the port was sloppy." I don't think that's it. The three platforms genuinely have different shapes, and almost nobody writes those shapes down. So I did.&lt;/p&gt;

&lt;p&gt;I maintain a palette library with 100 palettes across the three platforms (31 app, 34 web, 35 mini program). Every palette has exactly ten color roles. I compared the role sets, the state transitions and the contrast behavior. All figures below are computed from those 100 palettes, so they describe one particular library and not the industry.&lt;/p&gt;

&lt;p&gt;Here's what came out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ten roles each, and one shared field name
&lt;/h2&gt;

&lt;p&gt;Put the three lists side by side and the intersection is one field name: &lt;code&gt;bg&lt;/code&gt;. That's it. Everything else is named differently, or exists on one platform and not the others.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;App&lt;/th&gt;
&lt;th&gt;Web&lt;/th&gt;
&lt;th&gt;Mini program&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Primary&lt;/td&gt;
&lt;td&gt;&lt;code&gt;primary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;primary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;theme&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Darker / interaction&lt;/td&gt;
&lt;td&gt;&lt;code&gt;primaryDark&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;primaryHover&lt;/code&gt; + &lt;code&gt;primaryActive&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;btn&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lighter version&lt;/td&gt;
&lt;td&gt;&lt;code&gt;primaryLight&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tag&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secondary&lt;/td&gt;
&lt;td&gt;&lt;code&gt;secondary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accent&lt;/td&gt;
&lt;td&gt;&lt;code&gt;accent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;accent&lt;/code&gt; + &lt;code&gt;link&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;price&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text levels&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;text&lt;/code&gt; (one)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;textMain&lt;/code&gt; + &lt;code&gt;textSub&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;textMain&lt;/code&gt; + &lt;code&gt;textSub&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Divider&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;&lt;code&gt;border&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;divider&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Container&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;&lt;code&gt;surface&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;card&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Status&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;success&lt;/code&gt; / &lt;code&gt;warning&lt;/code&gt; / &lt;code&gt;danger&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disabled&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;&lt;code&gt;btnDisabled&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ten roles on each platform. Same count, different sets. Which explains the dead tokens: the mini program had no place to put &lt;code&gt;success&lt;/code&gt;, &lt;code&gt;warning&lt;/code&gt; or &lt;code&gt;danger&lt;/code&gt;, and no consumer for &lt;code&gt;border&lt;/code&gt;. Meanwhile it was missing three roles it actually needed, &lt;code&gt;price&lt;/code&gt;, &lt;code&gt;tag&lt;/code&gt; and &lt;code&gt;divider&lt;/code&gt;, because nobody had added them.&lt;/p&gt;

&lt;p&gt;So "one brand color across three platforms" is true of exactly one role. The primary can stay. The other nine have to be rewritten per platform, and someone has to decide what they become.&lt;/p&gt;

&lt;h2&gt;
  
  
  Web darkens along one line, the app spreads to both sides
&lt;/h2&gt;

&lt;p&gt;Web carries two interaction states. The app carries none, and the reason is the input device rather than the screen. A mouse pointer can hover. A finger has no middle state between pressing and lifting.&lt;/p&gt;

&lt;p&gt;Measured across the web palettes here, &lt;code&gt;primaryHover&lt;/code&gt; sits &lt;strong&gt;13.9 lightness points&lt;/strong&gt; below the primary (range 5.3 to 14.2), and &lt;code&gt;primaryActive&lt;/code&gt; sits &lt;strong&gt;26.1 points&lt;/strong&gt; below. The second is about &lt;strong&gt;1.9x&lt;/strong&gt; the first.&lt;/p&gt;

&lt;p&gt;The app does something structurally different. It gives the primary two flanks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;primaryDark&lt;/code&gt;: &lt;strong&gt;14.0 points&lt;/strong&gt; darker than the primary&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;primaryLight&lt;/code&gt;: &lt;strong&gt;32.0 points&lt;/strong&gt; lighter&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rise on the light side is &lt;strong&gt;2.3x&lt;/strong&gt; the drop on the dark side. That asymmetry is not arbitrary. The dark variant only ever marks a press. The light variant gets used as a tag background and as the fill of a selected area, which are large surfaces, and a fill that isn't light enough leaves no contrast headroom for dark text sitting on it. The dark side can move a little. The light side cannot.&lt;/p&gt;

&lt;p&gt;Working numbers if you're setting these by hand: darken hover by 10 to 14 points, take active to roughly twice that, and push the light variant about twice as far as the dark one in the opposite direction. Narrower than that and the press is invisible. Wider and one tap looks like a different brand.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mini program hides its accent behind a business word
&lt;/h2&gt;

&lt;p&gt;Web and app both call their accent &lt;code&gt;accent&lt;/code&gt;, and it is decorative: badges, highlights, anything that should be noticed without carrying information. The mini program has no &lt;code&gt;accent&lt;/code&gt;. It has &lt;code&gt;price&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That rename is the most useful thing in the whole comparison, because it exposes the rule underneath. In a mini program the accent serves a transaction, and price is its only job. Measured here, &lt;code&gt;price&lt;/code&gt; sits at a median &lt;strong&gt;4.50:1&lt;/strong&gt; against the background, with a low of &lt;strong&gt;3.65:1&lt;/strong&gt;. Compare that to the decorative accent on the other platforms, which in many palettes sits far below the body text threshold and is fine there.&lt;/p&gt;

&lt;p&gt;The generalizable version: &lt;strong&gt;whether a color needs to clear a contrast threshold depends on whether it carries information or atmosphere.&lt;/strong&gt; A decorative accent may sit low. An accent carrying a price, a count or a status may not. Same hue position, two uses, two standards, and the field name is doing the work of telling you which one you're looking at.&lt;/p&gt;

&lt;h2&gt;
  
  
  Each platform drops what it doesn't need, and the drops are the tell
&lt;/h2&gt;

&lt;p&gt;The omissions are not random. They line up with how each platform organizes a screen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Web&lt;/strong&gt; has &lt;code&gt;surface&lt;/code&gt; and &lt;code&gt;border&lt;/code&gt;, and no status colors. Long pages divide with hairlines and faint fills. A form can run two thousand pixels tall, and a rule tells you something changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The mini program&lt;/strong&gt; has &lt;code&gt;card&lt;/code&gt;, &lt;code&gt;divider&lt;/code&gt; and &lt;code&gt;tag&lt;/code&gt;, and no status colors either. The page is a card stack, one card per unit of information, and the gaps between cards do most of the dividing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The app&lt;/strong&gt; has &lt;code&gt;success&lt;/code&gt;, &lt;code&gt;warning&lt;/code&gt; and &lt;code&gt;danger&lt;/code&gt;, and neither &lt;code&gt;surface&lt;/code&gt; nor &lt;code&gt;border&lt;/code&gt;. Actions on a phone have consequences. A delete or a submit cannot be taken back, so state has to be explicit.&lt;/p&gt;

&lt;p&gt;Which settles the first question when porting: don't move colors first. Look at what establishes hierarchy on the target platform. Carrying web colors into a mini program without its spacing gives you cards that collapse into a smear. Carrying app colors to the web leaves three status roles with nowhere to live.&lt;/p&gt;

&lt;h2&gt;
  
  
  The exception worth knowing about
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;primaryLight&lt;/code&gt; figure above (32 points lighter) is a median. One palette in the library goes the other way: a primary of &lt;code&gt;#4c8df6&lt;/code&gt; at lightness 63, and a &lt;code&gt;primaryLight&lt;/code&gt; of &lt;code&gt;#1e3a63&lt;/code&gt; at lightness 25, darker by 38 points.&lt;/p&gt;

&lt;p&gt;That palette is a dark theme. Some dark implementations read &lt;code&gt;primaryLight&lt;/code&gt; as "the tinted container for the primary" and return a darkened surface instead of a lightened one. The name didn't change. The value did.&lt;/p&gt;

&lt;p&gt;The failure is concrete: if you treat &lt;code&gt;primaryLight&lt;/code&gt; as a light fill because of its name, that palette gives you dark navy behind dark text, and the contrast collapses.&lt;/p&gt;

&lt;p&gt;So the rule is one line: &lt;strong&gt;measure before you trust the name.&lt;/strong&gt; Compare the lightness of &lt;code&gt;primaryLight&lt;/code&gt; against the primary. Lighter, and it's a fill. Darker, and in this palette it's a deep container that wants light text on it. Same check applies to any palette you import and any design system you inherit.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you only ship one platform, you need four roles
&lt;/h2&gt;

&lt;p&gt;Most projects ship one platform, or ship one and expand later. You do not need thirty roles for that. Each platform has a minimum set, and four roles carry a page:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Minimum set&lt;/th&gt;
&lt;th&gt;What breaks first without it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Web&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;primary&lt;/code&gt; + &lt;code&gt;bg&lt;/code&gt; + &lt;code&gt;textMain&lt;/code&gt; + &lt;code&gt;border&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No &lt;code&gt;border&lt;/code&gt; and tables and forms lose their structure, leaving spacing to do all the work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mini program&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;theme&lt;/code&gt; + &lt;code&gt;bg&lt;/code&gt; + &lt;code&gt;textMain&lt;/code&gt; + &lt;code&gt;card&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No &lt;code&gt;card&lt;/code&gt; and the card stack flattens into one long column of text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;App&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;primary&lt;/code&gt; + &lt;code&gt;bg&lt;/code&gt; + &lt;code&gt;text&lt;/code&gt; + &lt;code&gt;primaryDark&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No &lt;code&gt;primaryDark&lt;/code&gt; and pressing a button produces no feedback at all&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Four roles get a usable interface out the door, with three gaps: no secondary text, no accent, and no disabled state. None of that blocks a launch. All of it shows up afterward, one "why does this look off" at a time.&lt;/p&gt;

&lt;p&gt;The order to fill the gaps is not a matter of taste. &lt;strong&gt;Secondary text first, then disabled states, then accent.&lt;/strong&gt; The first affects legibility, the last affects looks, and the middle one decides whether people think a button can be pressed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short list
&lt;/h2&gt;

&lt;p&gt;If you're doing the port this week:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Leave the primary alone.&lt;/strong&gt; Recognition depends on it and it has to match across platforms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web&lt;/strong&gt;: add &lt;code&gt;primaryHover&lt;/code&gt; (10 to 14 points darker), &lt;code&gt;primaryActive&lt;/code&gt; (about double), &lt;code&gt;border&lt;/code&gt;, &lt;code&gt;surface&lt;/code&gt;, &lt;code&gt;textSub&lt;/code&gt;. No status colors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mini program&lt;/strong&gt;: replace the accent with &lt;code&gt;price&lt;/code&gt; and hold it to the text standard. Add &lt;code&gt;card&lt;/code&gt;, &lt;code&gt;divider&lt;/code&gt;, &lt;code&gt;tag&lt;/code&gt;, &lt;code&gt;btnDisabled&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;App&lt;/strong&gt;: add &lt;code&gt;primaryDark&lt;/code&gt; (14 points darker), &lt;code&gt;primaryLight&lt;/code&gt; (32 points lighter, and verify it is actually lighter), &lt;code&gt;secondary&lt;/code&gt;, and the three status colors. No &lt;code&gt;surface&lt;/code&gt;, no &lt;code&gt;border&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dark themes&lt;/strong&gt;: check the direction of every variant. Names lie at the edges.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Where the numbers come from
&lt;/h2&gt;

&lt;p&gt;Every ratio in this post is computed with the WCAG 2.1 relative luminance formula, not estimated by eye, and every figure is regenerated at build time from the palette data rather than typed into the page. The full write-up, the datasets these figures come from, and two more pieces on secondary text contrast and on why card and border colors are nearly invisible, live at &lt;a href="https://paletteget.com/guides/" rel="noopener noreferrer"&gt;paletteget.com/guides&lt;/a&gt;. The thresholds and the method are documented at &lt;a href="https://paletteget.com/method/" rel="noopener noreferrer"&gt;paletteget.com/method&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want to check one of your own palettes, the contrast numbers for all three platforms are listed on each palette page, so you don't have to work them out by hand.&lt;/p&gt;

&lt;p&gt;One caveat worth stating plainly: this is 100 palettes from one library, curated by one person. It is a decent sample for spotting structural patterns in how the three platforms differ, and it is not a survey of what the industry does. Treat the ratios as evidence that the difference exists, not as targets to hit.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>design</category>
      <category>css</category>
      <category>ux</category>
    </item>
    <item>
      <title>I gave my coding agent a color palette library, and stopped pasting hex codes</title>
      <dc:creator>yang7206</dc:creator>
      <pubDate>Thu, 22 Oct 2026 14:05:41 +0000</pubDate>
      <link>https://dev.to/yang7206/i-gave-my-coding-agent-a-color-palette-library-and-stopped-pasting-hex-codes-2c83</link>
      <guid>https://dev.to/yang7206/i-gave-my-coding-agent-a-color-palette-library-and-stopped-pasting-hex-codes-2c83</guid>
      <description>&lt;p&gt;I asked Claude for a color palette for a finance app last month. It gave me six hex codes. They looked right in the chat window. In the actual UI, the body text sat on a primary color at about 2.8:1 contrast, which is unreadable for anyone who isn't 20/20, and the "dark mode" surface was a greenish gray that made every card look sick.&lt;/p&gt;

&lt;p&gt;The model wasn't being lazy. It has no ruler. It can produce a hex value that "looks like" a dark teal, but it cannot compute that white text on that teal lands at 2.8:1, because nothing in the conversation tells it what the number is. Ask a better question and you get a nicer-looking guess. That's the ceiling.&lt;/p&gt;

&lt;p&gt;So I built the ruler.&lt;/p&gt;

&lt;h2&gt;
  
  
  What PaletteGet is
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://paletteget.com/" rel="noopener noreferrer"&gt;PaletteGet&lt;/a&gt; is a small site with about 90 hand-picked palettes for three surfaces: mobile apps, web, and mini programs (the WeChat-style mini app format). Each palette ships with role colors, measured WCAG contrast for the pairs that matter, and a short editor note about when to use it.&lt;/p&gt;

&lt;p&gt;The part that matters here is that it's also an MCP server, so an agent can read it directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"paletteget"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://paletteget-mcp.y7206062.workers.dev/mcp"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Drop that into Cursor, Claude Desktop, or any client that speaks Streamable HTTP. No API key, no sign-up, no local process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four tools, on purpose
&lt;/h2&gt;

&lt;p&gt;I kept the surface small because tool sprawl makes agents worse, not better:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;list_palettes&lt;/code&gt; filters by platform and tag&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_palette&lt;/code&gt; returns one palette's role colors, contrast, and the editor note&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;suggest_palette&lt;/code&gt; takes a plain-language query ("dark finance app")&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_css_variables&lt;/code&gt; returns paste-ready custom properties&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. Four tools, read-only.&lt;/p&gt;

&lt;h2&gt;
  
  
  The conversation that convinced me
&lt;/h2&gt;

&lt;p&gt;Here is the exchange, trimmed:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Me:&lt;/strong&gt; I need a palette for a dark-mode finance app. Numbers need to be legible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent:&lt;/strong&gt; &lt;em&gt;(calls &lt;code&gt;suggest_palette&lt;/code&gt; with "dark finance app")&lt;/em&gt; &lt;a href="https://paletteget.com/palette-list/app/mo-lan-ye/" rel="noopener noreferrer"&gt;Ink Blue Night&lt;/a&gt; is the closest fit, tagged for finance and analytics apps in dark mode. Background &lt;code&gt;#12161f&lt;/code&gt;. Body text &lt;code&gt;#e8ecf4&lt;/code&gt; reads at 15.29:1, AAA. The primary &lt;code&gt;#4c8df6&lt;/code&gt; sits at 5.56:1 against the background: AA for body copy, AAA for large text, so primary-colored links and icons hold up on the page. The amber accent &lt;code&gt;#f2b33d&lt;/code&gt; lands at 9.73:1, safe for prices and deltas. One catch: white on the primary measures 3.26:1, enough for large text only, so keep the button fill for big bold labels.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Read that last sentence again. The agent didn't just hand me colors, it told me &lt;em&gt;where each one is allowed&lt;/em&gt;. That distinction is what a contrast number buys you: the model stops asserting and starts measuring.&lt;/p&gt;

&lt;p&gt;Before, I'd get "here's a nice palette" and find out it was wrong after wiring it up. Now the constraint arrives with the proposal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just prompt better
&lt;/h2&gt;

&lt;p&gt;Two reasons.&lt;/p&gt;

&lt;p&gt;First, arithmetic is not what LLMs are good at, and contrast is arithmetic with a specific formula (relative luminance, then a ratio). A model can approximate it and be wrong exactly often enough to hurt.&lt;/p&gt;

&lt;p&gt;Second, the failure is silent. A palette that fails contrast still looks fine to the person choosing it, because contrast failure is a property of &lt;em&gt;use&lt;/em&gt; (text on a surface at a size), not of the color swatch. You only discover it when a real paragraph lands on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CSS shortcut
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;get_css_variables&lt;/code&gt; is the tool I use most, because it collapses two steps into one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* Ink Blue Night */&lt;/span&gt;
&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--color-primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#4c8df6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-primary-dark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#2f6bd4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-primary-light&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#1e3a63&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-secondary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#8b7cf6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-accent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f2b33d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#3fbf7f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-warning&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e8b341&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-danger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f2685e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#12161f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e8ecf4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Semantic names, not &lt;code&gt;--blue-500&lt;/code&gt;. If I later swap palettes, the components don't change.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this is not
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Not a generator. Roughly 90 palettes, curated, and it grows by one or two a day at most. If you want infinite procedural color, this is the wrong tool.&lt;/li&gt;
&lt;li&gt;Not a design system. It gives you color roles and contrast, not spacing, type scales, or components.&lt;/li&gt;
&lt;li&gt;Not a hosted service with an SLA. It's a read-only endpoint on a Worker.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Endpoint: &lt;code&gt;https://paletteget-mcp.y7206062.workers.dev/mcp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Docs: &lt;a href="https://paletteget.com/docs/" rel="noopener noreferrer"&gt;https://paletteget.com/docs/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source: &lt;a href="https://github.com/yang7206/paletteget-mcp" rel="noopener noreferrer"&gt;https://github.com/yang7206/paletteget-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Registry: &lt;code&gt;io.github.yang7206/paletteget&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your agent keeps inventing hex values that fall apart in the browser, give it the ruler and see what changes. Feedback welcome, especially palettes you want that aren't there.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>design</category>
      <category>css</category>
    </item>
  </channel>
</rss>
