# A brand color picker for Storyblok, with a contrast check built in

*Editors pick from the brand palette, and see right away whether text on that color stays readable*

By Dominik Angerer · 4 November 2026 · https://d12r.io/e/storyblok-brand-color-picker-field-plugin/

**TL;DR:** A Storyblok field that limits colors to your brand palette and shows a live WCAG contrast check. Copy the prompt and build it yourself.

A free hex input is how a landing page ends up in "almost our teal". This field gives editors the brand palette as swatches and tells them, while they choose, whether white or dark text on that color is readable.

> **Try it yourself**
>
> Paste this into Claude Code or Cursor:
>
> ```
> Fetch and follow the Storyblok field plugin instructions at https://raw.githubusercontent.com/DominikAngerer/storyblok-field-wizard/main/generator/PROMPT.md and build this field:
> A color picker limited to a brand palette, shown as clickable swatches (a radio group), that stores { "hex": "#00b3b0", "name": "Teal" } with hex always lowercase 6-digit, and null when nothing is selected; an older value stored as a plain hex string is read as { hex, name: null }. Option palette is a comma-separated list of Name:#hex entries (default Teal:#00b3b0,Navy:#1b243f,Orange:#ffac00,Red:#ff6159,Blue:#395ece,Green:#2db47d); the name is optional, an unnamed entry uses its hex as name, 3-digit hex is expanded, and invalid entries are skipped. Option allowCustom (default false in code, true in the sandbox config) adds a custom hex input with a native color picker and a Use button (Enter also applies); invalid input shows an inline error and disables Use, a custom color is stored with name null, but if it equals a palette color it gets that palette name. Below the swatches, show a preview of the chosen color with its name and hex, a live WCAG 2.x contrast check against white and dark (#101525) text with the ratio and a rating (AAA, AA, AA Large, Fail), and a Clear button.
> ```
>
> Already have the Claude Code plugin? Run:
>
> ```
> /storyblok-field:storyblok-field "A color picker limited to our brand palette, with an optional custom hex input and a live WCAG contrast check against white and dark text"
> ```
>
> It takes about three minutes and needs Node 22+. You only need a Storyblok personal access token if you want to deploy.

> **Good to know: plugin names are global**
>
> Field plugin names in Storyblok are shared across all spaces, not per space. If the field doesn't render correctly in the preview, or the deploy fails with `DUPLICATED_NAME`, someone else probably has that name already. Give yours a different one, for example `@yourhandle/brand-color-picker`. It happened to me with the [code editor field](https://d12r.io/e/storyblok-code-editor-field-plugin/).

## What it's for

Brand colors live in a design system, but in a CMS they often turn into a text field where anyone can type anything, and you get section backgrounds that are slightly off, or white text on a yellow banner that nobody can read. This field keeps editors on the palette, and if your team allows custom colors, it still shows the contrast result before anything goes live. Accessibility becomes part of picking a color, not a review step afterwards. (WCAG is the accessibility standard for readable text.)

## What you get

![Teal picked from six palette swatches, with a preview header and contrast results for white and dark text](https://a.storyblok.com/f/296339501219281/2144x1928/1d0c6cb8a8/brand-color-picker-02-palette-teal-7bfcd4eb.png)

*Teal picked from the palette. The contrast panel shows the ratio and WCAG rating for white and dark text.*

![An invalid custom hex shows an inline error and the Use button is disabled](https://a.storyblok.com/f/296339501219281/2144x1980/d9fb784bfc/brand-color-picker-04-custom-invalid-88db16ca.png)

*Custom hex input with an inline error. Nothing is saved until the value is valid.*

![With a custom palette and allowCustom off, three swatches and no custom input](https://a.storyblok.com/f/296339501219281/2144x1434/fcdf0572aa/brand-color-picker-10-variant-options-ac3376dc.png)

*A custom palette with an invalid entry (skipped) and custom colors turned off.*

The stored value:

```json
{ "hex": "#00b3b0", "name": "Teal" }
```

| Option | Default | What it does |
| --- | --- | --- |
| `palette` | `Teal:#00b3b0,Navy:#1b243f,…` | Comma-separated `Name:#hex` entries. The name is optional. |
| `allowCustom` | `false` (sandbox: `true`) | `true` lets editors enter any hex color. |

## Using it in the frontend

```jsx
<section style={{ background: blok.color?.hex ?? 'transparent' }}>…</section>
```

## What I learned building it

- **This was my reference field.** I built it in [my main Claude session](https://d12r.io/e/coordinator-pattern-agents-subagents-handoffs/), before handing any field to fresh agents, to see whether Storyblok's docs, the field plugin CLI, the starter template and a prompt were enough. They were: about 270 lines of field code and 6 tests, green on the first try. It then became the example for how options get parsed and old values get read.
- **Good to know: options are always strings.** `allowCustom` arrives as `"true"`, not `true`, and a value set in `field-plugin.config.json` is only the sandbox default. For a real component, the value is set per field in the component schema. That's why the code default is `false` while the sandbox says `true`.
- **The browser tests found a gap in the docs, not in the code.** An unnamed palette entry stores its hex as `name`. That's defensible, but a frontend printing `name` would print `#00ff00`. PLUGIN.md now says so.
- **The contrast maths held up.** The browser walkthrough compared the ratios with an independent WCAG calculation, and also checked 3-digit and uppercase hex, and that a custom color matching the palette gets the palette name.

More background in [the article about the original experiment](https://d12r.io/e/midnight-idea-generating-storyblok-field-plugins/). The other fields: [product picker](https://d12r.io/e/storyblok-product-picker-field-plugin/), [author picker](https://d12r.io/e/storyblok-author-picker-field-plugin/), [recipe picker](https://d12r.io/e/storyblok-recipe-picker-fullscreen-field-plugin/), [code editor](https://d12r.io/e/storyblok-code-editor-field-plugin/).
