Pure TailwindMIT licensed

Tailwind Color Swatch Picker Dropdown

An 18-swatch palette in a dropdown, each swatch scaling up on hover with a pure-CSS tooltip. Two things are worth the port: the swatch colours are inline custom properties (eighteen one-off hex values are not a design scale), and the tooltip is after:content-[attr(data-name)] — an attr() read, in a utility.

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

<section class="tdd-15 w-full min-h-svh grid place-items-center bg-linear-[135deg,var(--color-tdd15-bg1),var(--color-tdd15-bg2)] font-tdd15-sans text-tdd15-ink px-5 py-10" aria-label="Colour swatch picker dropdown demo">
  <div class="flex flex-col items-center gap-6">
    <div class="group relative z-10 after:absolute after:inset-x-0 after:top-full after:h-2.5 after:content-['']">
      <button type="button" style="--current:#6366f1" class="flex min-h-11 cursor-pointer items-center gap-2 rounded-[10px] border border-tdd15-edge bg-white px-4 py-2.5 font-tdd15-sans text-sm font-medium text-tdd15-ink shadow-[0_1px_4px_oklch(0_0_0/0.05)] transition-[box-shadow,border-color] duration-150 group-hover:border-tdd15-ring group-hover:shadow-[0_4px_16px_oklch(0_0_0/0.1)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-acc">
        <span class="size-4.5 shrink-0 rounded-full border-2 border-white/80 bg-[var(--current)] shadow-[0_0_0_1px_oklch(0_0_0/0.12)]" aria-hidden="true"></span>
        <span>Color</span>
        <span class="text-[11px] text-tdd15-mut transition-transform duration-200 group-hover:rotate-180 group-focus-within:rotate-180" aria-hidden="true">&#9662;</span>
      </button>
      <div class="pointer-events-none absolute top-[calc(100%+10px)] left-1/2 w-55 -translate-x-1/2 -translate-y-1.5 rounded-2xl border border-tdd15-edge bg-white p-3.5 opacity-0 shadow-[0_12px_40px_oklch(0_0_0/0.12)] transition-[opacity,translate] duration-280 ease-tdd15 group-hover:pointer-events-auto group-hover:translate-y-0 group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:translate-y-0 group-focus-within:opacity-100 motion-reduce:duration-150">
        <p class="mb-2.5 text-[11px] font-semibold uppercase tracking-[0.08em] text-tdd15-mut">Choose a Color</p>
        <div class="grid grid-cols-6 gap-1.5">
          <button type="button" data-name="Red" style="--sw:#ef4444" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Orange" style="--sw:#f97316" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Amber" style="--sw:#f59e0b" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Yellow" style="--sw:#eab308" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Lime" style="--sw:#84cc16" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Green" style="--sw:#22c55e" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Emerald" style="--sw:#10b981" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Teal" style="--sw:#14b8a6" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Cyan" style="--sw:#06b6d4" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Sky" style="--sw:#0ea5e9" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Blue" style="--sw:#3b82f6" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Indigo" style="--sw:#6366f1" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Violet" style="--sw:#8b5cf6" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Purple" style="--sw:#a855f7" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Fuchsia" style="--sw:#d946ef" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Pink" style="--sw:#ec4899" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Rose" style="--sw:#f43f5e" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Slate" style="--sw:#64748b" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
        </div>
      </div>
    </div>
    <p class="text-[12.5px] italic text-tdd15-mut">Hover or tab the button to pick a palette colour</p>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-tdd15-bg1: #fafafa;
  --color-tdd15-bg1: oklch(0.98 0 0);
  --color-tdd15-bg2: #f4f4f5;
  --color-tdd15-bg2: oklch(0.96 0.002 285);
  --color-tdd15-ink: #0f172a;
  --color-tdd15-ink: oklch(0.21 0.04 265);
  --color-tdd15-mut: #64748b;
  --color-tdd15-mut: oklch(0.55 0.03 255);
  --color-tdd15-edge: #e2e8f0;
  --color-tdd15-edge: oklch(0.92 0.01 255);
  --color-tdd15-ring: #c7d2fe;
  --color-tdd15-ring: oklch(0.86 0.06 275);
  --color-tdd15-acc: #6366f1;
  --color-tdd15-acc: oklch(0.59 0.2 278);
  --font-tdd15-sans: 'Inter', system-ui, sans-serif;
  --ease-tdd15: cubic-bezier(0.16, 1, 0.3, 1);
}

.tdd-15 {
  width: 100%;
  min-height: 100svh;
}

.tdd-15 ::selection {
  background: var(--color-tdd15-acc);
  color: #fff;
}

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-tdd15-bg1: #fafafa;
  --color-tdd15-bg1: oklch(0.98 0 0);
  --color-tdd15-bg2: #f4f4f5;
  --color-tdd15-bg2: oklch(0.96 0.002 285);
  --color-tdd15-ink: #0f172a;
  --color-tdd15-ink: oklch(0.21 0.04 265);
  --color-tdd15-mut: #64748b;
  --color-tdd15-mut: oklch(0.55 0.03 255);
  --color-tdd15-edge: #e2e8f0;
  --color-tdd15-edge: oklch(0.92 0.01 255);
  --color-tdd15-ring: #c7d2fe;
  --color-tdd15-ring: oklch(0.86 0.06 275);
  --color-tdd15-acc: #6366f1;
  --color-tdd15-acc: oklch(0.59 0.2 278);
  --font-tdd15-sans: 'Inter', system-ui, sans-serif;
  --ease-tdd15: cubic-bezier(0.16, 1, 0.3, 1);
}

.tdd-15 {
  width: 100%;
  min-height: 100svh;
}

.tdd-15 ::selection {
  background: var(--color-tdd15-acc);
  color: #fff;
}
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=Inter:wght@400;500;600&display=swap">

What changed in the port

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

  • after:content-[attr(data-name)] is the demo's headline: a data-driven tooltip that stays entirely in the class list.
  • Eighteen inline --sw values instead of eighteen @theme colours — the clearest example in the collection of what should NOT become a token.
  • The swatches became <button>s with focus styling, turning an unreachable div grid into a real control.
  • hover:z-20 next to hover:scale-[1.22] keeps cause and effect adjacent; in CSS the z-index fix lived in a different rule from the scale that required it.
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 Color Swatch Picker Dropdown
Source: https://codefronts.com/navigation/tailwind-dropdown-menus/tailwind-color-swatch-picker-dropdown/

An 18-swatch palette in a dropdown, each swatch scaling up on hover with a pure-CSS tooltip. Two things are worth the port: the swatch colours are inline custom properties (eighteen one-off hex values are not a design scale), and the tooltip is after:content-[attr(data-name)] — an attr() read, in a utility.
## HTML
```html
<section class="tdd-15 w-full min-h-svh grid place-items-center bg-linear-[135deg,var(--color-tdd15-bg1),var(--color-tdd15-bg2)] font-tdd15-sans text-tdd15-ink px-5 py-10" aria-label="Colour swatch picker dropdown demo">
  <div class="flex flex-col items-center gap-6">
    <div class="group relative z-10 after:absolute after:inset-x-0 after:top-full after:h-2.5 after:content-['']">
      <button type="button" style="--current:#6366f1" class="flex min-h-11 cursor-pointer items-center gap-2 rounded-[10px] border border-tdd15-edge bg-white px-4 py-2.5 font-tdd15-sans text-sm font-medium text-tdd15-ink shadow-[0_1px_4px_oklch(0_0_0/0.05)] transition-[box-shadow,border-color] duration-150 group-hover:border-tdd15-ring group-hover:shadow-[0_4px_16px_oklch(0_0_0/0.1)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-acc">
        <span class="size-4.5 shrink-0 rounded-full border-2 border-white/80 bg-[var(--current)] shadow-[0_0_0_1px_oklch(0_0_0/0.12)]" aria-hidden="true"></span>
        <span>Color</span>
        <span class="text-[11px] text-tdd15-mut transition-transform duration-200 group-hover:rotate-180 group-focus-within:rotate-180" aria-hidden="true">&#9662;</span>
      </button>
      <div class="pointer-events-none absolute top-[calc(100%+10px)] left-1/2 w-55 -translate-x-1/2 -translate-y-1.5 rounded-2xl border border-tdd15-edge bg-white p-3.5 opacity-0 shadow-[0_12px_40px_oklch(0_0_0/0.12)] transition-[opacity,translate] duration-280 ease-tdd15 group-hover:pointer-events-auto group-hover:translate-y-0 group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:translate-y-0 group-focus-within:opacity-100 motion-reduce:duration-150">
        <p class="mb-2.5 text-[11px] font-semibold uppercase tracking-[0.08em] text-tdd15-mut">Choose a Color</p>
        <div class="grid grid-cols-6 gap-1.5">
          <button type="button" data-name="Red" style="--sw:#ef4444" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Orange" style="--sw:#f97316" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Amber" style="--sw:#f59e0b" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Yellow" style="--sw:#eab308" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Lime" style="--sw:#84cc16" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Green" style="--sw:#22c55e" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Emerald" style="--sw:#10b981" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Teal" style="--sw:#14b8a6" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Cyan" style="--sw:#06b6d4" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Sky" style="--sw:#0ea5e9" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Blue" style="--sw:#3b82f6" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Indigo" style="--sw:#6366f1" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Violet" style="--sw:#8b5cf6" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Purple" style="--sw:#a855f7" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Fuchsia" style="--sw:#d946ef" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Pink" style="--sw:#ec4899" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Rose" style="--sw:#f43f5e" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
          <button type="button" data-name="Slate" style="--sw:#64748b" class="relative size-6.5 cursor-pointer rounded-md border-0 bg-[var(--sw)] shadow-[0_1px_3px_oklch(0_0_0/0.15)] transition-[scale,box-shadow] duration-150 after:pointer-events-none after:absolute after:bottom-[calc(100%+6px)] after:left-1/2 after:-translate-x-1/2 after:rounded-[5px] after:bg-tdd15-ink after:px-1.5 after:py-0.5 after:text-[10px] after:font-medium after:whitespace-nowrap after:text-white after:opacity-0 after:transition-opacity after:delay-100 after:duration-150 after:content-[attr(data-name)] hover:z-20 hover:scale-[1.22] hover:after:opacity-100 focus-visible:z-20 focus-visible:scale-[1.22] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tdd15-ink focus-visible:after:opacity-100"></button>
        </div>
      </div>
    </div>
    <p class="text-[12.5px] italic text-tdd15-mut">Hover or tab the button to pick a palette colour</p>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tdd15-bg1: #fafafa;
  --color-tdd15-bg1: oklch(0.98 0 0);
  --color-tdd15-bg2: #f4f4f5;
  --color-tdd15-bg2: oklch(0.96 0.002 285);
  --color-tdd15-ink: #0f172a;
  --color-tdd15-ink: oklch(0.21 0.04 265);
  --color-tdd15-mut: #64748b;
  --color-tdd15-mut: oklch(0.55 0.03 255);
  --color-tdd15-edge: #e2e8f0;
  --color-tdd15-edge: oklch(0.92 0.01 255);
  --color-tdd15-ring: #c7d2fe;
  --color-tdd15-ring: oklch(0.86 0.06 275);
  --color-tdd15-acc: #6366f1;
  --color-tdd15-acc: oklch(0.59 0.2 278);
  --font-tdd15-sans: 'Inter', system-ui, sans-serif;
  --ease-tdd15: cubic-bezier(0.16, 1, 0.3, 1);
}

.tdd-15 {
  width: 100%;
  min-height: 100svh;
}

.tdd-15 ::selection {
  background: var(--color-tdd15-acc);
  color: #fff;
}
```

How this works

The tooltip is the swatch. after:content-[attr(data-name)] pulls the label straight off the element's attribute, so eighteen tooltips cost eighteen attributes and zero extra nodes:

<button data-name="Indigo" style="--sw:#6366f1"
  class="bg-[var(--sw)] after:content-[attr(data-name)]">

Scale needs stacking. A swatch at 1.22 overlaps its neighbours, so it also takes hover:z-20 — otherwise it grows underneath the next one in source order.

The swatches are buttons, not divs: a colour picker is a control, and the CSS version's <div> grid is unreachable by keyboard. Focus gets the same scale and tooltip as hover.

Make it yours

  • Palette size: grid-cols-6 → grid-cols-8 and the swatches reflow with no other change.
  • Selected ring: add aria-pressed and style it with aria-pressed:ring-2 aria-pressed:ring-offset-2.
  • Grouping: a heading row with col-span-full between families.
  • Tooltip placement: it is bottom-[calc(100%+6px)]; flip to top-full for the bottom row if the panel is near a screen edge.

Gotchas — read before shipping

  • attr() only works for content in stable CSS — you cannot read a colour out of an attribute into background. That is why the colour is a custom property.
  • The tooltip must be pointer-events-none, or moving toward it cancels the hover on the swatch beneath.
  • Scaling inside a grid can shift neighbours unless the cells are fixed-size; the swatches carry an explicit size-6.5.
  • Eighteen theme tokens for eighteen swatches would pollute the utility namespace for no gain — inline properties are correct here.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

attr() for content and CSS Grid are universally supported; attr() with a type argument is not, and is not used.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Theme tokensset in @theme, used by a utility

Search CodeFronts

Loading…