Tailwind + JSMIT licensed

Tailwind Colour Picker Input

A native colour swatch welded to a hex field — and, unlike the CSS original, the two actually talk to each other. Twelve lines of JavaScript keep swatch and text in sync both ways, which is the entire point of showing them together.

Live Demo
Plain CSSTailwind v4
Try it

Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time

Untitled UI React · Affiliate link

Get Untitled UI Pro

The code

<div class="tif-19 grid min-h-svh w-full place-items-center bg-tif19-bg px-6 py-14 font-tif19-mono" aria-label="Colour picker input demo">
  <label class="grid w-full max-w-70 gap-1.5">
    <span class="text-[10px] tracking-[0.12em] text-tif19-mut uppercase">Brand colour</span>
    <span class="flex items-stretch overflow-hidden rounded-[10px] border border-white/12 bg-tif19-field transition-colors duration-200 focus-within:border-tif19-acc">
      <input type="color" value="#7C6CFF" aria-label="Pick colour" data-tif19-swatch
        class="size-11 shrink-0 cursor-pointer border-0 border-r border-white/10 bg-transparent p-1.5 outline-none [&::-webkit-color-swatch]:rounded-md [&::-webkit-color-swatch]:border-0 [&::-webkit-color-swatch-wrapper]:p-0">
      <input type="text" value="#7C6CFF" aria-label="Hex value" name="tif19-hex" pattern="^#[0-9A-Fa-f]{6}$" spellcheck="false" data-tif19-text
        class="min-w-0 flex-1 border-0 bg-transparent px-3.5 text-[13px] font-semibold tracking-[0.04em] text-tif19-ink uppercase outline-none user-invalid:text-tif19-bad">
    </span>
  </label>
</div>
@import "tailwindcss";

@theme {
  --color-tif19-bg: #12121a;
  --color-tif19-bg: oklch(0.19 0.015 285);
  --color-tif19-field: #1a1a22;
  --color-tif19-field: oklch(0.23 0.015 285);
  --color-tif19-ink: #f0eeff;
  --color-tif19-ink: oklch(0.95 0.02 290);
  --color-tif19-mut: #b8b6d4;
  --color-tif19-mut: oklch(0.77 0.03 290);
  --color-tif19-acc: #7c6cff;
  --color-tif19-acc: oklch(0.62 0.2 288);
  --color-tif19-bad: #ef4444;
  --color-tif19-bad: oklch(0.63 0.22 27);
  --font-tif19-mono: 'JetBrains Mono', ui-monospace, monospace;
}

.tif-19 {
  width: 100%;
}

.tif-19 ::selection {
  background: var(--color-tif19-acc);
  color: #12121a;
}
(function () {
  document.querySelectorAll('.tif-19').forEach(function (root) {
    if (root.dataset.bound) return; root.dataset.bound = '1';
    var swatch = root.querySelector('[data-tif19-swatch]');
    var text = root.querySelector('[data-tif19-text]');
    if (!swatch || !text) return;
    swatch.addEventListener('input', function () {
      text.value = swatch.value.toUpperCase();
    });
    text.addEventListener('input', function () {
      if (/^#[0-9a-f]{6}$/i.test(text.value.trim())) swatch.value = text.value.trim();
    });
  });
})();

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>

Compiles in the browser — perfect for a CodePen or a quick test, not for production.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@import "tailwindcss";

@theme {
  --color-tif19-bg: #12121a;
  --color-tif19-bg: oklch(0.19 0.015 285);
  --color-tif19-field: #1a1a22;
  --color-tif19-field: oklch(0.23 0.015 285);
  --color-tif19-ink: #f0eeff;
  --color-tif19-ink: oklch(0.95 0.02 290);
  --color-tif19-mut: #b8b6d4;
  --color-tif19-mut: oklch(0.77 0.03 290);
  --color-tif19-acc: #7c6cff;
  --color-tif19-acc: oklch(0.62 0.2 288);
  --color-tif19-bad: #ef4444;
  --color-tif19-bad: oklch(0.63 0.22 27);
  --font-tif19-mono: 'JetBrains Mono', ui-monospace, monospace;
}

.tif-19 {
  width: 100%;
}

.tif-19 ::selection {
  background: var(--color-tif19-acc);
  color: #12121a;
}
3Fonts used by this demooptional — skip if you have your own type stack
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap">

What changed in the port

Which parts became utilities, which stayed CSS, and why.

  • Improved, not just ported. The CSS original's own gotcha list says the two fields never sync; twelve lines fix it, and the demo finally demonstrates what it depicts.
  • The swatch is size-11 (44px) rather than 44px reached by adding padding to a 32px box — same result, stated once.
  • user-invalid:text-tif19-bad gives the hex field inline feedback that the original left to submit-time validation.
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Colour Picker Input
Source: https://codefronts.com/components/tailwind-css-input-fields/tailwind-colour-picker-input/

A native colour swatch welded to a hex field — and, unlike the CSS original, the two actually talk to each other. Twelve lines of JavaScript keep swatch and text in sync both ways, which is the entire point of showing them together.
## HTML
```html
<div class="tif-19 grid min-h-svh w-full place-items-center bg-tif19-bg px-6 py-14 font-tif19-mono" aria-label="Colour picker input demo">
  <label class="grid w-full max-w-70 gap-1.5">
    <span class="text-[10px] tracking-[0.12em] text-tif19-mut uppercase">Brand colour</span>
    <span class="flex items-stretch overflow-hidden rounded-[10px] border border-white/12 bg-tif19-field transition-colors duration-200 focus-within:border-tif19-acc">
      <input type="color" value="#7C6CFF" aria-label="Pick colour" data-tif19-swatch
        class="size-11 shrink-0 cursor-pointer border-0 border-r border-white/10 bg-transparent p-1.5 outline-none [&::-webkit-color-swatch]:rounded-md [&::-webkit-color-swatch]:border-0 [&::-webkit-color-swatch-wrapper]:p-0">
      <input type="text" value="#7C6CFF" aria-label="Hex value" name="tif19-hex" pattern="^#[0-9A-Fa-f]{6}$" spellcheck="false" data-tif19-text
        class="min-w-0 flex-1 border-0 bg-transparent px-3.5 text-[13px] font-semibold tracking-[0.04em] text-tif19-ink uppercase outline-none user-invalid:text-tif19-bad">
    </span>
  </label>
</div>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tif19-bg: #12121a;
  --color-tif19-bg: oklch(0.19 0.015 285);
  --color-tif19-field: #1a1a22;
  --color-tif19-field: oklch(0.23 0.015 285);
  --color-tif19-ink: #f0eeff;
  --color-tif19-ink: oklch(0.95 0.02 290);
  --color-tif19-mut: #b8b6d4;
  --color-tif19-mut: oklch(0.77 0.03 290);
  --color-tif19-acc: #7c6cff;
  --color-tif19-acc: oklch(0.62 0.2 288);
  --color-tif19-bad: #ef4444;
  --color-tif19-bad: oklch(0.63 0.22 27);
  --font-tif19-mono: 'JetBrains Mono', ui-monospace, monospace;
}

.tif-19 {
  width: 100%;
}

.tif-19 ::selection {
  background: var(--color-tif19-acc);
  color: #12121a;
}
```

## JavaScript
```js
(function () {
  document.querySelectorAll('.tif-19').forEach(function (root) {
    if (root.dataset.bound) return; root.dataset.bound = '1';
    var swatch = root.querySelector('[data-tif19-swatch]');
    var text = root.querySelector('[data-tif19-text]');
    if (!swatch || !text) return;
    swatch.addEventListener('input', function () {
      text.value = swatch.value.toUpperCase();
    });
    text.addEventListener('input', function () {
      if (/^#[0-9a-f]{6}$/i.test(text.value.trim())) swatch.value = text.value.trim();
    });
  });
})();
```

How this works

Two inputs, one value. The colour input emits lowercase #rrggbb on change; the text field mirrors it, and a valid hex typed into the text field pushes back:

swatch.addEventListener('input', function () { text.value = swatch.value.toUpperCase(); });
text.addEventListener('input', function () {
  if (/^#[0-9a-f]{6}$/i.test(text.value)) swatch.value = text.value;
});

The picker UI is the OS. Chrome, Safari and Firefox each open their own dialog and none of them is restylable — what you control is the closed swatch, which is why it is worth getting right.

Make it yours

  • Initial value: the value on both inputs; keep them equal or the field lies on load.
  • Shorthand: widen the pattern to accept 3-digit or 8-digit hex, and normalise in the handler.
  • Accent: --color-tif19-acc for the focus border.
  • Copy button: a third child reading navigator.clipboard.writeText(text.value).

Gotchas — read before shipping

  • input type=&quot;color&quot; cannot express alpha or any non-hex format. Convert yourself if you need RGBA.
  • Safari returns lowercase hex from the macOS panel; uppercase in the text field is a display choice you must apply on every sync.
  • The swatch cannot be made larger than the OS picker's intrinsic hit area on some platforms — do not design around a 64px chip.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

Native colour input is universal; the picker dialog is the OS and not restylable beyond the swatch pseudos.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Search CodeFronts

Loading…