Pure TailwindMIT licensed

Tailwind Material Design Chips with Live Filtering

All four M3 chip types — assist, filter, input and suggestion — and then the part the CSS version could only fake: the filter chips actually filter. Checking a chip reveals its matching cards through a has-[#id:checked]: rule on the results grid, so a live filter runs with no JavaScript at all.

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="tmd-08 w-full min-h-svh bg-tmd08-bg font-tmd08-sans text-tmd08-onsurface px-5 py-9" aria-label="Material Design chips demo">
  <div class="w-[min(900px,100%)] mx-auto grid gap-6">
    <header class="grid gap-1.5">
      <h2 class="text-[26px] font-medium tracking-[-0.01em]">Chips that actually filter</h2>
      <p class="text-sm text-tmd08-onvariant">Four M3 chip types, and a working facet filter built from <code class="font-tmd08-mono text-[12px]">has-[…:checked]:*:data-[tag]:block</code>.</p>
    </header>

    <div class="group/filter grid gap-4 p-5.5 rounded-3xl bg-tmd08-surface shadow-tmd08-1">
      <span class="font-tmd08-mono text-[11px] uppercase tracking-[0.1em] text-tmd08-onvariant">Filter chips &mdash; live</span>
      <div class="flex flex-wrap gap-2">
        <input type="checkbox" id="tmd08-f1" class="peer/f1 sr-only">
        <label for="tmd08-f1" class="relative overflow-hidden inline-flex items-center gap-1.5 min-h-11 px-4 rounded-lg cursor-pointer border border-tmd08-outline text-sm font-medium text-tmd08-onsurface transition-colors duration-200 group-has-[#tmd08-f1:checked]/filter:bg-tmd08-secondarycontainer group-has-[#tmd08-f1:checked]/filter:text-tmd08-onsecondarycontainer group-has-[#tmd08-f1:checked]/filter:border-transparent peer-focus-visible/f1:outline-2 peer-focus-visible/f1:outline-offset-2 peer-focus-visible/f1:outline-tmd08-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd08-icon shrink-0 w-0 overflow-hidden text-[18px] leading-none transition-[width] duration-200 group-has-[#tmd08-f1:checked]/filter:w-4.5 motion-reduce:transition-none" aria-hidden="true">check</span>Deep water</label>
        <input type="checkbox" id="tmd08-f2" class="peer/f2 sr-only">
        <label for="tmd08-f2" class="relative overflow-hidden inline-flex items-center gap-1.5 min-h-11 px-4 rounded-lg cursor-pointer border border-tmd08-outline text-sm font-medium text-tmd08-onsurface transition-colors duration-200 group-has-[#tmd08-f2:checked]/filter:bg-tmd08-secondarycontainer group-has-[#tmd08-f2:checked]/filter:text-tmd08-onsecondarycontainer group-has-[#tmd08-f2:checked]/filter:border-transparent peer-focus-visible/f2:outline-2 peer-focus-visible/f2:outline-offset-2 peer-focus-visible/f2:outline-tmd08-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd08-icon shrink-0 w-0 overflow-hidden text-[18px] leading-none transition-[width] duration-200 group-has-[#tmd08-f2:checked]/filter:w-4.5 motion-reduce:transition-none" aria-hidden="true">check</span>Coastal</label>
        <input type="checkbox" id="tmd08-f3" class="peer/f3 sr-only">
        <label for="tmd08-f3" class="relative overflow-hidden inline-flex items-center gap-1.5 min-h-11 px-4 rounded-lg cursor-pointer border border-tmd08-outline text-sm font-medium text-tmd08-onsurface transition-colors duration-200 group-has-[#tmd08-f3:checked]/filter:bg-tmd08-secondarycontainer group-has-[#tmd08-f3:checked]/filter:text-tmd08-onsecondarycontainer group-has-[#tmd08-f3:checked]/filter:border-transparent peer-focus-visible/f3:outline-2 peer-focus-visible/f3:outline-offset-2 peer-focus-visible/f3:outline-tmd08-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd08-icon shrink-0 w-0 overflow-hidden text-[18px] leading-none transition-[width] duration-200 group-has-[#tmd08-f3:checked]/filter:w-4.5 motion-reduce:transition-none" aria-hidden="true">check</span>Estuary</label>
      </div>

      <div class="grid gap-2 grid-cols-[repeat(auto-fit,minmax(min(200px,100%),1fr))] group-has-[:checked]/filter:*:hidden group-has-[#tmd08-f1:checked]/filter:*:data-[tag=deep]:block group-has-[#tmd08-f2:checked]/filter:*:data-[tag=coastal]:block group-has-[#tmd08-f3:checked]/filter:*:data-[tag=estuary]:block">
        <article data-tag="deep" class="grid gap-1 p-4 rounded-2xl bg-tmd08-surface2"><b class="text-sm font-medium">ST-014 &middot; 1 240 m</b><span class="text-xs text-tmd08-onvariant">Deep water &middot; North Atlantic</span></article>
        <article data-tag="coastal" class="grid gap-1 p-4 rounded-2xl bg-tmd08-surface2"><b class="text-sm font-medium">ST-059 &middot; 312 m</b><span class="text-xs text-tmd08-onvariant">Coastal &middot; Irish Shelf</span></article>
        <article data-tag="estuary" class="grid gap-1 p-4 rounded-2xl bg-tmd08-surface2"><b class="text-sm font-medium">ST-022 &middot; 86 m</b><span class="text-xs text-tmd08-onvariant">Estuary &middot; Baltic</span></article>
        <article data-tag="deep" class="grid gap-1 p-4 rounded-2xl bg-tmd08-surface2"><b class="text-sm font-medium">ST-047 &middot; 2 010 m</b><span class="text-xs text-tmd08-onvariant">Deep water &middot; Norwegian Sea</span></article>
      </div>
      <p class="text-xs text-tmd08-onvariant">Unchecked shows everything; checking a chip narrows the grid. No script &mdash; the grid states its own filter rules.</p>
    </div>

    <div class="grid gap-4 p-5.5 rounded-3xl bg-tmd08-surface shadow-tmd08-1">
      <span class="font-tmd08-mono text-[11px] uppercase tracking-[0.1em] text-tmd08-onvariant">Assist, suggestion &amp; elevated</span>
      <div class="flex flex-wrap gap-2">
        <button type="button" class="relative overflow-hidden inline-flex items-center gap-2 min-h-11 px-4 rounded-lg cursor-pointer bg-transparent border border-tmd08-outline text-sm font-medium text-tmd08-onsurface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd08-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08] active:before:opacity-[0.12]"><span class="font-tmd08-icon text-[18px] leading-none text-tmd08-primary" aria-hidden="true">event</span>Schedule dive</button>
        <button type="button" class="relative overflow-hidden inline-flex items-center gap-2 min-h-11 px-4 rounded-lg cursor-pointer bg-transparent border border-tmd08-outline text-sm font-medium text-tmd08-onsurface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd08-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08] active:before:opacity-[0.12]"><span class="font-tmd08-icon text-[18px] leading-none text-tmd08-primary" aria-hidden="true">near_me</span>Nearest station</button>
        <button type="button" class="relative overflow-hidden inline-flex items-center min-h-11 px-4 rounded-lg cursor-pointer bg-tmd08-surface border-0 shadow-tmd08-1 text-sm font-medium text-tmd08-onsurface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd08-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]">Elevated chip</button>
        <button type="button" class="relative overflow-hidden inline-flex items-center min-h-11 px-4 rounded-lg cursor-pointer bg-transparent border border-tmd08-outlinevariant text-sm font-medium text-tmd08-onvariant focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd08-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]">Suggestion</button>
        <span aria-disabled="true" class="inline-flex items-center min-h-11 px-4 rounded-lg border border-tmd08-outline text-sm font-medium text-tmd08-onsurface opacity-38 pointer-events-none">Disabled</span>
      </div>
    </div>

    <div class="grid gap-4 p-5.5 rounded-3xl bg-tmd08-surface shadow-tmd08-1">
      <span class="font-tmd08-mono text-[11px] uppercase tracking-[0.1em] text-tmd08-onvariant">Input chips &mdash; with remove controls</span>
      <div class="flex flex-wrap gap-2">
        <span class="inline-flex items-center gap-1 min-h-11 pl-1.5 pr-1 rounded-lg border border-tmd08-outline text-sm has-[button:focus-visible]:ring-2 has-[button:focus-visible]:ring-tmd08-primary"><span class="grid place-items-center w-7 h-7 rounded-full bg-tmd08-primary text-white font-tmd08-mono text-[11px]">MK</span>M. Kovač<button type="button" aria-label="Remove M. Kovač" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-lg border-0 bg-transparent cursor-pointer text-tmd08-onvariant focus-visible:outline-none before:absolute before:inset-2 before:rounded-full before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd08-icon text-[18px] leading-none" aria-hidden="true">close</span></button></span>
        <span class="inline-flex items-center gap-1 min-h-11 pl-3 pr-1 rounded-lg border border-tmd08-outline text-sm has-[button:focus-visible]:ring-2 has-[button:focus-visible]:ring-tmd08-primary">depth &gt; 500 m<button type="button" aria-label="Remove depth filter" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-lg border-0 bg-transparent cursor-pointer text-tmd08-onvariant focus-visible:outline-none before:absolute before:inset-2 before:rounded-full before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd08-icon text-[18px] leading-none" aria-hidden="true">close</span></button></span>
        <span class="inline-flex items-center gap-1 min-h-11 pl-3 pr-1 rounded-lg bg-tmd08-secondarycontainer text-tmd08-onsecondarycontainer text-sm has-[button:focus-visible]:ring-2 has-[button:focus-visible]:ring-tmd08-primary">QC flagged<button type="button" aria-label="Remove QC filter" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-lg border-0 bg-transparent cursor-pointer text-current focus-visible:outline-none before:absolute before:inset-2 before:rounded-full before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd08-icon text-[18px] leading-none" aria-hidden="true">close</span></button></span>
      </div>
      <p class="text-xs text-tmd08-onvariant">The chip shows a ring when its remove button takes focus &mdash; <span class="font-tmd08-mono text-[11px]">has-[button:focus-visible]:ring-2</span>, the wrapper reacting to a descendant.</p>
    </div>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-tmd08-bg: #fce4ec;
  --color-tmd08-bg: oklch(0.941 0.028 355.4);
  --color-tmd08-surface: #ffffff;
  --color-tmd08-surface: oklch(1 0 89.9);
  --color-tmd08-surface2: #fdf0f4;
  --color-tmd08-surface2: oklch(0.966 0.015 356.6);
  --color-tmd08-primary: #ad1457;
  --color-tmd08-primary: oklch(0.49 0.187 2.5);
  --color-tmd08-secondarycontainer: #ffd8e4;
  --color-tmd08-secondarycontainer: oklch(0.918 0.046 357.6);
  --color-tmd08-onsecondarycontainer: #31111d;
  --color-tmd08-onsecondarycontainer: oklch(0.232 0.054 358.6);
  --color-tmd08-onsurface: #1f1a1c;
  --color-tmd08-onsurface: oklch(0.224 0.009 351.7);
  --color-tmd08-onvariant: #5c4650;
  --color-tmd08-onvariant: oklch(0.421 0.034 349.6);
  --color-tmd08-outline: #85737b;
  --color-tmd08-outline: oklch(0.575 0.025 349.3);
  --color-tmd08-outlinevariant: #e4c8d2;
  --color-tmd08-outlinevariant: oklch(0.859 0.034 354.3);
  --font-tmd08-sans: 'Roboto Flex', 'Roboto', system-ui, sans-serif;
  --font-tmd08-mono: 'Roboto Mono', ui-monospace, monospace;
  --font-tmd08-icon: 'Material Symbols Outlined';
  --shadow-tmd08-1: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 1px 3px 1px rgb(0 0 0 / 0.15);
}

.tmd-08 {
  width: 100%;
  min-height: 100svh;
}

.tmd-08 .font-tmd08-icon {
  font-feature-settings: 'liga';
}

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-tmd08-bg: #fce4ec;
  --color-tmd08-bg: oklch(0.941 0.028 355.4);
  --color-tmd08-surface: #ffffff;
  --color-tmd08-surface: oklch(1 0 89.9);
  --color-tmd08-surface2: #fdf0f4;
  --color-tmd08-surface2: oklch(0.966 0.015 356.6);
  --color-tmd08-primary: #ad1457;
  --color-tmd08-primary: oklch(0.49 0.187 2.5);
  --color-tmd08-secondarycontainer: #ffd8e4;
  --color-tmd08-secondarycontainer: oklch(0.918 0.046 357.6);
  --color-tmd08-onsecondarycontainer: #31111d;
  --color-tmd08-onsecondarycontainer: oklch(0.232 0.054 358.6);
  --color-tmd08-onsurface: #1f1a1c;
  --color-tmd08-onsurface: oklch(0.224 0.009 351.7);
  --color-tmd08-onvariant: #5c4650;
  --color-tmd08-onvariant: oklch(0.421 0.034 349.6);
  --color-tmd08-outline: #85737b;
  --color-tmd08-outline: oklch(0.575 0.025 349.3);
  --color-tmd08-outlinevariant: #e4c8d2;
  --color-tmd08-outlinevariant: oklch(0.859 0.034 354.3);
  --font-tmd08-sans: 'Roboto Flex', 'Roboto', system-ui, sans-serif;
  --font-tmd08-mono: 'Roboto Mono', ui-monospace, monospace;
  --font-tmd08-icon: 'Material Symbols Outlined';
  --shadow-tmd08-1: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 1px 3px 1px rgb(0 0 0 / 0.15);
}

.tmd-08 {
  width: 100%;
  min-height: 100svh;
}

.tmd-08 .font-tmd08-icon {
  font-feature-settings: 'liga';
}
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=Roboto+Flex:opsz,wght@8..144,300..700&family=Roboto+Mono:wght@400;500&family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0..1,0&display=swap">

What changed in the port

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

  • The filter is real, and that is the demo's argument: has-[#id:checked]:*:data-[tag=x]:block lets an ancestor's state select matching children, which is a JS filter loop deleted for small sets.
  • The checkmark's w-0 → w-4.5 width transition is the M3 detail most ports miss; animating opacity alone leaves the chip the wrong width at rest.
  • Filter chips are checkboxes; assist and suggestion chips are buttons; input chips are neither, because they contain a button. Keeping those three semantics distinct is what the CSS version's uniform .chip class hid.
  • has-[button:focus-visible]:ring-2 is the wrapper-reacts-to-descendant direction of :has(), and it is the only way to show chip focus without moving the ring off the real control.
  • opacity-38 pointer-events-none aria-disabled together are the honest disabled chip — the CSS version's opacity alone left it clickable.
  • Where to stop: past three facets the class list on the results grid becomes unreadable. The notes say so rather than pretending CSS scales here.
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 Material Design Chips with Live Filtering
Source: https://codefronts.com/design-styles/tailwind-material-design-components/tailwind-material-design-chips-live-filtering/

All four M3 chip types &mdash; assist, filter, input and suggestion &mdash; and then the part the CSS version could only fake: the filter chips actually filter. Checking a chip reveals its matching cards through a has-[#id:checked]: rule on the results grid, so a live filter runs with no JavaScript at all.
## HTML
```html
<section class="tmd-08 w-full min-h-svh bg-tmd08-bg font-tmd08-sans text-tmd08-onsurface px-5 py-9" aria-label="Material Design chips demo">
  <div class="w-[min(900px,100%)] mx-auto grid gap-6">
    <header class="grid gap-1.5">
      <h2 class="text-[26px] font-medium tracking-[-0.01em]">Chips that actually filter</h2>
      <p class="text-sm text-tmd08-onvariant">Four M3 chip types, and a working facet filter built from <code class="font-tmd08-mono text-[12px]">has-[…:checked]:*:data-[tag]:block</code>.</p>
    </header>

    <div class="group/filter grid gap-4 p-5.5 rounded-3xl bg-tmd08-surface shadow-tmd08-1">
      <span class="font-tmd08-mono text-[11px] uppercase tracking-[0.1em] text-tmd08-onvariant">Filter chips &mdash; live</span>
      <div class="flex flex-wrap gap-2">
        <input type="checkbox" id="tmd08-f1" class="peer/f1 sr-only">
        <label for="tmd08-f1" class="relative overflow-hidden inline-flex items-center gap-1.5 min-h-11 px-4 rounded-lg cursor-pointer border border-tmd08-outline text-sm font-medium text-tmd08-onsurface transition-colors duration-200 group-has-[#tmd08-f1:checked]/filter:bg-tmd08-secondarycontainer group-has-[#tmd08-f1:checked]/filter:text-tmd08-onsecondarycontainer group-has-[#tmd08-f1:checked]/filter:border-transparent peer-focus-visible/f1:outline-2 peer-focus-visible/f1:outline-offset-2 peer-focus-visible/f1:outline-tmd08-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd08-icon shrink-0 w-0 overflow-hidden text-[18px] leading-none transition-[width] duration-200 group-has-[#tmd08-f1:checked]/filter:w-4.5 motion-reduce:transition-none" aria-hidden="true">check</span>Deep water</label>
        <input type="checkbox" id="tmd08-f2" class="peer/f2 sr-only">
        <label for="tmd08-f2" class="relative overflow-hidden inline-flex items-center gap-1.5 min-h-11 px-4 rounded-lg cursor-pointer border border-tmd08-outline text-sm font-medium text-tmd08-onsurface transition-colors duration-200 group-has-[#tmd08-f2:checked]/filter:bg-tmd08-secondarycontainer group-has-[#tmd08-f2:checked]/filter:text-tmd08-onsecondarycontainer group-has-[#tmd08-f2:checked]/filter:border-transparent peer-focus-visible/f2:outline-2 peer-focus-visible/f2:outline-offset-2 peer-focus-visible/f2:outline-tmd08-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd08-icon shrink-0 w-0 overflow-hidden text-[18px] leading-none transition-[width] duration-200 group-has-[#tmd08-f2:checked]/filter:w-4.5 motion-reduce:transition-none" aria-hidden="true">check</span>Coastal</label>
        <input type="checkbox" id="tmd08-f3" class="peer/f3 sr-only">
        <label for="tmd08-f3" class="relative overflow-hidden inline-flex items-center gap-1.5 min-h-11 px-4 rounded-lg cursor-pointer border border-tmd08-outline text-sm font-medium text-tmd08-onsurface transition-colors duration-200 group-has-[#tmd08-f3:checked]/filter:bg-tmd08-secondarycontainer group-has-[#tmd08-f3:checked]/filter:text-tmd08-onsecondarycontainer group-has-[#tmd08-f3:checked]/filter:border-transparent peer-focus-visible/f3:outline-2 peer-focus-visible/f3:outline-offset-2 peer-focus-visible/f3:outline-tmd08-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd08-icon shrink-0 w-0 overflow-hidden text-[18px] leading-none transition-[width] duration-200 group-has-[#tmd08-f3:checked]/filter:w-4.5 motion-reduce:transition-none" aria-hidden="true">check</span>Estuary</label>
      </div>

      <div class="grid gap-2 grid-cols-[repeat(auto-fit,minmax(min(200px,100%),1fr))] group-has-[:checked]/filter:*:hidden group-has-[#tmd08-f1:checked]/filter:*:data-[tag=deep]:block group-has-[#tmd08-f2:checked]/filter:*:data-[tag=coastal]:block group-has-[#tmd08-f3:checked]/filter:*:data-[tag=estuary]:block">
        <article data-tag="deep" class="grid gap-1 p-4 rounded-2xl bg-tmd08-surface2"><b class="text-sm font-medium">ST-014 &middot; 1 240 m</b><span class="text-xs text-tmd08-onvariant">Deep water &middot; North Atlantic</span></article>
        <article data-tag="coastal" class="grid gap-1 p-4 rounded-2xl bg-tmd08-surface2"><b class="text-sm font-medium">ST-059 &middot; 312 m</b><span class="text-xs text-tmd08-onvariant">Coastal &middot; Irish Shelf</span></article>
        <article data-tag="estuary" class="grid gap-1 p-4 rounded-2xl bg-tmd08-surface2"><b class="text-sm font-medium">ST-022 &middot; 86 m</b><span class="text-xs text-tmd08-onvariant">Estuary &middot; Baltic</span></article>
        <article data-tag="deep" class="grid gap-1 p-4 rounded-2xl bg-tmd08-surface2"><b class="text-sm font-medium">ST-047 &middot; 2 010 m</b><span class="text-xs text-tmd08-onvariant">Deep water &middot; Norwegian Sea</span></article>
      </div>
      <p class="text-xs text-tmd08-onvariant">Unchecked shows everything; checking a chip narrows the grid. No script &mdash; the grid states its own filter rules.</p>
    </div>

    <div class="grid gap-4 p-5.5 rounded-3xl bg-tmd08-surface shadow-tmd08-1">
      <span class="font-tmd08-mono text-[11px] uppercase tracking-[0.1em] text-tmd08-onvariant">Assist, suggestion &amp; elevated</span>
      <div class="flex flex-wrap gap-2">
        <button type="button" class="relative overflow-hidden inline-flex items-center gap-2 min-h-11 px-4 rounded-lg cursor-pointer bg-transparent border border-tmd08-outline text-sm font-medium text-tmd08-onsurface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd08-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08] active:before:opacity-[0.12]"><span class="font-tmd08-icon text-[18px] leading-none text-tmd08-primary" aria-hidden="true">event</span>Schedule dive</button>
        <button type="button" class="relative overflow-hidden inline-flex items-center gap-2 min-h-11 px-4 rounded-lg cursor-pointer bg-transparent border border-tmd08-outline text-sm font-medium text-tmd08-onsurface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd08-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08] active:before:opacity-[0.12]"><span class="font-tmd08-icon text-[18px] leading-none text-tmd08-primary" aria-hidden="true">near_me</span>Nearest station</button>
        <button type="button" class="relative overflow-hidden inline-flex items-center min-h-11 px-4 rounded-lg cursor-pointer bg-tmd08-surface border-0 shadow-tmd08-1 text-sm font-medium text-tmd08-onsurface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd08-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]">Elevated chip</button>
        <button type="button" class="relative overflow-hidden inline-flex items-center min-h-11 px-4 rounded-lg cursor-pointer bg-transparent border border-tmd08-outlinevariant text-sm font-medium text-tmd08-onvariant focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tmd08-primary before:absolute before:inset-0 before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]">Suggestion</button>
        <span aria-disabled="true" class="inline-flex items-center min-h-11 px-4 rounded-lg border border-tmd08-outline text-sm font-medium text-tmd08-onsurface opacity-38 pointer-events-none">Disabled</span>
      </div>
    </div>

    <div class="grid gap-4 p-5.5 rounded-3xl bg-tmd08-surface shadow-tmd08-1">
      <span class="font-tmd08-mono text-[11px] uppercase tracking-[0.1em] text-tmd08-onvariant">Input chips &mdash; with remove controls</span>
      <div class="flex flex-wrap gap-2">
        <span class="inline-flex items-center gap-1 min-h-11 pl-1.5 pr-1 rounded-lg border border-tmd08-outline text-sm has-[button:focus-visible]:ring-2 has-[button:focus-visible]:ring-tmd08-primary"><span class="grid place-items-center w-7 h-7 rounded-full bg-tmd08-primary text-white font-tmd08-mono text-[11px]">MK</span>M. Kovač<button type="button" aria-label="Remove M. Kovač" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-lg border-0 bg-transparent cursor-pointer text-tmd08-onvariant focus-visible:outline-none before:absolute before:inset-2 before:rounded-full before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd08-icon text-[18px] leading-none" aria-hidden="true">close</span></button></span>
        <span class="inline-flex items-center gap-1 min-h-11 pl-3 pr-1 rounded-lg border border-tmd08-outline text-sm has-[button:focus-visible]:ring-2 has-[button:focus-visible]:ring-tmd08-primary">depth &gt; 500 m<button type="button" aria-label="Remove depth filter" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-lg border-0 bg-transparent cursor-pointer text-tmd08-onvariant focus-visible:outline-none before:absolute before:inset-2 before:rounded-full before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd08-icon text-[18px] leading-none" aria-hidden="true">close</span></button></span>
        <span class="inline-flex items-center gap-1 min-h-11 pl-3 pr-1 rounded-lg bg-tmd08-secondarycontainer text-tmd08-onsecondarycontainer text-sm has-[button:focus-visible]:ring-2 has-[button:focus-visible]:ring-tmd08-primary">QC flagged<button type="button" aria-label="Remove QC filter" class="relative overflow-hidden grid place-items-center min-w-11 min-h-11 rounded-lg border-0 bg-transparent cursor-pointer text-current focus-visible:outline-none before:absolute before:inset-2 before:rounded-full before:bg-current before:opacity-0 before:transition-opacity hover:before:opacity-[0.08]"><span class="font-tmd08-icon text-[18px] leading-none" aria-hidden="true">close</span></button></span>
      </div>
      <p class="text-xs text-tmd08-onvariant">The chip shows a ring when its remove button takes focus &mdash; <span class="font-tmd08-mono text-[11px]">has-[button:focus-visible]:ring-2</span>, the wrapper reacting to a descendant.</p>
    </div>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tmd08-bg: #fce4ec;
  --color-tmd08-bg: oklch(0.941 0.028 355.4);
  --color-tmd08-surface: #ffffff;
  --color-tmd08-surface: oklch(1 0 89.9);
  --color-tmd08-surface2: #fdf0f4;
  --color-tmd08-surface2: oklch(0.966 0.015 356.6);
  --color-tmd08-primary: #ad1457;
  --color-tmd08-primary: oklch(0.49 0.187 2.5);
  --color-tmd08-secondarycontainer: #ffd8e4;
  --color-tmd08-secondarycontainer: oklch(0.918 0.046 357.6);
  --color-tmd08-onsecondarycontainer: #31111d;
  --color-tmd08-onsecondarycontainer: oklch(0.232 0.054 358.6);
  --color-tmd08-onsurface: #1f1a1c;
  --color-tmd08-onsurface: oklch(0.224 0.009 351.7);
  --color-tmd08-onvariant: #5c4650;
  --color-tmd08-onvariant: oklch(0.421 0.034 349.6);
  --color-tmd08-outline: #85737b;
  --color-tmd08-outline: oklch(0.575 0.025 349.3);
  --color-tmd08-outlinevariant: #e4c8d2;
  --color-tmd08-outlinevariant: oklch(0.859 0.034 354.3);
  --font-tmd08-sans: 'Roboto Flex', 'Roboto', system-ui, sans-serif;
  --font-tmd08-mono: 'Roboto Mono', ui-monospace, monospace;
  --font-tmd08-icon: 'Material Symbols Outlined';
  --shadow-tmd08-1: 0 1px 2px 0 rgb(0 0 0 / 0.3), 0 1px 3px 1px rgb(0 0 0 / 0.15);
}

.tmd-08 {
  width: 100%;
  min-height: 100svh;
}

.tmd-08 .font-tmd08-icon {
  font-feature-settings: 'liga';
}
```

How this works

A filter chip is a checkbox that grows a tick.

<div class="group/filter">
  <input type="checkbox" id="tmd08-f1" class="sr-only">
  <label for="tmd08-f1" class="… group-has-[#tmd08-f1:checked]/filter:bg-tmd08-secondarycontainer">
    <span class="w-0 overflow-hidden transition-[width]
      group-has-[#tmd08-f1:checked]/filter:w-4.5">check</span>
  Deep water
</label>

The tick's w-0 → w-4.5 width transition is why the M3 chip feels alive; animating opacity alone leaves a gap in the resting state.

The filter really filters. The results grid states, in utilities, which children to show for which chip:

<div class="group/filter"> … chips …
  <div class="… group-has-[#tmd08-f1:checked]/filter:*:data-[tag=deep]:block">

For a handful of items that is genuinely better than JS: it cannot get out of sync, it survives a re-render, and it costs no script. Past a few dozen items or several simultaneous facets, the combinatorics win and you want real code — the notes say where the line is.

Input chips keep a real button. The remove control is a <button aria-label="Remove …">, not a decorative ×, and the chip shows a ring when that button has focus via has-[button:focus-visible]:.

Make it yours

  • Chip height is min-h-11 for the 44px target with the M3 32px look coming from py-*; keep the target, shrink the paint.
  • Selected colour: --color-tmd08-secondarycontainer drives every chip type's checked state.
  • Add a facet by copying a chip and adding one has-[#id:checked]:*:data-[tag=x]:block utility to the grid.
  • For elevated chips swap the border for shadow-tmd08-1; for outlined drop the shadow.
  • Delete the trailing dismiss button on input chips if the chip is not user-removable — an inert × is worse than none.

Gotchas — read before shipping

  • The *: child variant only reaches direct children, and the state must come from a named group on an ancestor of BOTH the chips and the grid — a bare has-[…:checked]: on the grid matches nothing, because the checkboxes are not inside it.
  • CSS-only faceted filtering is O(facets × items) in class lists. Two or three facets is fine; five is a stylesheet you cannot read, and that is the moment for JS.
  • A chip that removes itself must move focus somewhere before it disappears, or focus falls to <body>. That is a JS concern, which is why this demo's input chips are static.
  • Never nest a <button> inside a <label> that toggles a checkbox — clicking the button also toggles. The input chips here are not labels for that reason.
  • w-0 overflow-hidden on the tick needs shrink-0 too, or flex will negotiate the width back.
  • Disabled chips need aria-disabled plus pointer-events-none; opacity-38 alone leaves them clickable.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

The live filter needs :has() (Chrome 105, Safari 15.4, Firefox 121). Without it every card stays visible — the chips still select, the filter simply does not apply, which is the right degradation for a filter.

Techniques used in this demo

Theme tokensset in @theme, used by a utility

HTML & JSno Tailwind involved

Search CodeFronts

Loading…