SolutionsCase StudiesAI ToolsResourcesAboutContact
Get Your Free AI Automation Audit Book a Free 30-Minute Strategy Call
Playground~/frontend Open editor

~/css/tags-chips

8 CSS Tags and Chips

5 Pure CSS3 with JSPublished 2026-10-04Free · no sign-up

Eight tag and chip patterns: topic links, colour-coded labels, a tag input that turns text into chips, single-choice chips, people chips with avatars, filter chips with counts, outline tags with icons and a long list with a show-more toggle.

01. Topic Tag Links

TagsLinks

Article topic tags as a list of links, each a rounded pill with a hash prefix added in CSS. Tags that lead somewhere should be links, not buttons, and the list has a label so screen readers announce Topics before the tags.

View code
<ul class="tpc" aria-label="Topics">
  <li><a href="#">ceramics</a></li><li><a href="#">small business</a></li><li><a href="#">packaging</a></li><li><a href="#">photography</a></li>
</ul>
.tpc { box-sizing: border-box; max-width: 100%; display: flex; flex-wrap: wrap; gap: 8px; margin: 0; padding: 0; list-style: none; font: 600 13.5px Inter, system-ui, sans-serif; }
.tpc a { display: inline-block; padding: 6px 12px; border-radius: 999px; background: #f2f4f7; color: #344054; text-decoration: none; }
.tpc a::before { content: "#"; margin-right: 2px; color: #5d6679; }
.tpc a:hover { background: #e4e7ec; color: #101828; }
.tpc a:focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; }

02. Colour-Coded Label Chips

LabelsColour

Issue-tracker labels such as Bug, Feature and Docs, each with a coloured dot and a tinted background derived from one custom property using color-mix(). Every label also has distinct text, so colour is never the only difference.

View code
<div class="lbl">
  <span style="--c:#f87171">Bug</span><span style="--c:#34d399">Feature</span><span style="--c:#60a5fa">Docs</span><span style="--c:#fbbf24">Needs review</span><span style="--c:#c084fc">Design</span>
</div>
.lbl { box-sizing: border-box; max-width: 100%; width: 340px; display: flex; flex-wrap: wrap; gap: 8px; font: 600 13px Inter, system-ui, sans-serif; }
.lbl span { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border: 1px solid color-mix(in srgb, var(--c) 45%, transparent); border-radius: 999px; background: color-mix(in srgb, var(--c) 16%, transparent); color: #fff; }
.lbl span::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--c); }

03. Tag Input Field

FormJavaScript

A field where pressing Enter or a comma turns the typed text into a tag, and Backspace on an empty field removes the last tag. Tags are kept in a list with remove buttons, and a hidden input holds the comma-separated value for form submission.

View code
<div class="tin">
  <label for="tin-i">Skills</label>
  <div class="box"><ul id="tin-l"></ul><input id="tin-i" placeholder="Type and press Enter" autocomplete="off"></div>
  <input type="hidden" name="skills" id="tin-v">
</div>
.tin { box-sizing: border-box; max-width: 100%; width: 360px; font-family: Inter, system-ui, sans-serif; }
.tin label { display: block; margin-bottom: 6px; color: #344054; font-weight: 600; font-size: 14px; }
.box { display: flex; flex-wrap: wrap; gap: 6px; padding: 6px; border: 1.5px solid #d0d5dd; border-radius: 10px; background: #fff; }
.box:focus-within { border-color: #2563eb; box-shadow: 0 0 0 3px rgba(37,99,235,.15); }
.box ul { display: contents; margin: 0; padding: 0; list-style: none; }
.box li { display: inline-flex; align-items: center; gap: 4px; padding: 3px 4px 3px 10px; border-radius: 999px; background: #eef4ff; color: #1e3a8a; font-size: 13px; }
.box li button { width: 20px; height: 20px; border: 0; border-radius: 50%; background: #c7d7fe; color: #1e3a8a; cursor: pointer; }
.box input { flex: 1; min-width: 120px; padding: 6px; border: 0; font: 15px Inter, sans-serif; outline: none; }
const tl = document.getElementById('tin-l'), ti = document.getElementById('tin-i'), tv = document.getElementById('tin-v');
let tags = ['CSS', 'Figma'];
function draw() {
  tl.innerHTML = '';
  tags.forEach((t, i) => {
    const li = document.createElement('li'); li.textContent = t;
    const b = document.createElement('button'); b.type = 'button'; b.textContent = '×'; b.setAttribute('aria-label', 'Remove ' + t);
    b.addEventListener('click', () => { tags.splice(i, 1); draw(); ti.focus(); });
    li.appendChild(b); tl.appendChild(li);
  });
  tv.value = tags.join(',');
}
ti.addEventListener('keydown', e => {
  if ((e.key === 'Enter' || e.key === ',') && ti.value.trim()) { e.preventDefault(); if (!tags.includes(ti.value.trim())) tags.push(ti.value.trim()); ti.value = ''; draw(); }
  else if (e.key === 'Backspace' && !ti.value && tags.length) { tags.pop(); draw(); }
});
draw();

04. Choice Chips

RadioSelection

Single-select chips for options like delivery time, built from radio buttons so only one can be chosen and the arrow keys move between them. The selected chip fills with colour and gains a tick through the :checked state.

View code
<fieldset class="chc">
  <legend>Delivery window</legend>
  <label><input type="radio" name="dw" checked>Morning</label>
  <label><input type="radio" name="dw">Afternoon</label>
  <label><input type="radio" name="dw">Evening</label>
</fieldset>
.chc { box-sizing: border-box; max-width: 100%; min-inline-size: 0; display: flex; flex-wrap: wrap; gap: 8px; margin: 0; padding: 0; border: 0; font: 600 14px Inter, system-ui, sans-serif; color: #e7ebf5; }
.chc legend { width: 100%; margin-bottom: 10px; color: #9aa4b8; font-size: 13px; }
.chc label { display: inline-flex; align-items: center; gap: 6px; padding: 7px 14px; border: 1.5px solid #343c51; border-radius: 999px; cursor: pointer; }
.chc input { position: absolute; opacity: 0; }
.chc label:has(input:checked) { border-color: #fbbf24; background: #fbbf24; color: #1a1205; }
.chc label:has(input:checked)::before { content: "\2713"; }
.chc label:has(input:focus-visible) { outline: 2px solid #fde68a; outline-offset: 2px; }

05. Person Chips with Avatars

PeopleChips

Chips that represent people, with a small avatar, a name and an optional remove button, as used for assignees and mentions. The avatar initials are decorative because the full name follows, which keeps screen reader output tidy.

View code
<ul class="pch" aria-label="Assigned to">
  <li><span aria-hidden="true" style="--c:#f472b6">MK</span>Maya Khan</li>
  <li><span aria-hidden="true" style="--c:#60a5fa">TO</span>Tunde Obi</li>
  <li><span aria-hidden="true" style="--c:#34d399">EL</span>Elif Lund</li>
</ul>
.pch { box-sizing: border-box; max-width: 100%; display: flex; flex-wrap: wrap; gap: 8px; margin: 0; padding: 0; list-style: none; font: 600 13.5px Inter, system-ui, sans-serif; }
.pch li { display: inline-flex; align-items: center; gap: 8px; padding: 3px 12px 3px 3px; border: 1px solid #e4e7ec; border-radius: 999px; background: #fff; color: #101828; }
.pch span { display: grid; place-items: center; width: 26px; height: 26px; border-radius: 50%; background: var(--c); color: #0e1118; font-size: 10.5px; font-weight: 800; }

06. Category Chips with Counts

FilterCount

Category filter chips that show how many items each contains, like Mugs 24. The counts are part of each button's text so they are read aloud, and the selected chip uses aria-pressed with a filled style.

View code
<div class="cnt" role="group" aria-label="Categories">
  <button aria-pressed="true">All <b>86</b></button><button aria-pressed="false">Mugs <b>24</b></button><button aria-pressed="false">Bowls <b>18</b></button><button aria-pressed="false">Vases <b>11</b></button>
</div>
.cnt { box-sizing: border-box; max-width: 100%; width: 340px; display: flex; flex-wrap: wrap; gap: 8px; font: 600 13.5px Inter, system-ui, sans-serif; }
.cnt button { display: inline-flex; align-items: center; gap: 6px; padding: 6px 8px 6px 12px; border: 1px solid #343c51; border-radius: 999px; background: #151a26; color: #e7ebf5; font: inherit; cursor: pointer; }
.cnt b { padding: 1px 7px; border-radius: 999px; background: #262d3e; font-size: 11.5px; }
.cnt [aria-pressed=true] { border-color: #e7ebf5; background: #e7ebf5; color: #0e1118; }
.cnt [aria-pressed=true] b { background: #0e1118; color: #fff; }
.cnt button:focus-visible { outline: 2px solid #9db8ff; outline-offset: 2px; }
const cb = document.querySelectorAll('.cnt button');
cb.forEach(b => b.addEventListener('click', () => cb.forEach(x => x.setAttribute('aria-pressed', String(x === b)))));

07. Outline and Icon Tags

TagsIcons

Minimal outline tags with small leading icons for attributes like Vegan, Gluten free and Spicy. The icons are decorative emoji hidden from screen readers, and the text alone carries the meaning, which also makes the tags translatable.

View code
<ul class="otg" aria-label="Dietary information">
  <li><span aria-hidden="true">&#127793;</span>Vegan</li><li><span aria-hidden="true">&#127838;</span>Gluten free</li><li><span aria-hidden="true">&#127798;&#65039;</span>Spicy</li>
</ul>
.otg { box-sizing: border-box; max-width: 100%; display: flex; flex-wrap: wrap; gap: 8px; margin: 0; padding: 0; list-style: none; font: 600 13px Inter, system-ui, sans-serif; }
.otg li { display: inline-flex; align-items: center; gap: 6px; padding: 5px 11px; border: 1.5px solid #101828; border-radius: 8px; color: #101828; }

08. Tag List with More Toggle

OverflowJavaScript

A long tag list that shows the first few tags and a +4 more button that reveals the rest. The button reports its state with aria-expanded and changes to Show less, so nothing is permanently hidden from any visitor.

View code
<div class="ovf">
  <ul id="ovf-l"><li>React</li><li>Vue</li><li>Svelte</li><li class="x">Astro</li><li class="x">Remix</li><li class="x">Solid</li><li class="x">Qwik</li></ul>
  <button aria-expanded="false" aria-controls="ovf-l">+4 more</button>
</div>
.ovf { box-sizing: border-box; max-width: 100%; width: 340px; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; font: 600 13px Inter, system-ui, sans-serif; }
.ovf ul { display: contents; margin: 0; padding: 0; list-style: none; }
.ovf li { padding: 5px 11px; border-radius: 999px; background: #1e2433; color: #e7ebf5; }
.ovf:not(.open) .x { display: none; }
.ovf button { padding: 5px 11px; border: 1px dashed #4a5368; border-radius: 999px; background: none; color: #9db8ff; font: inherit; cursor: pointer; }
.ovf button:focus-visible { outline: 2px solid #9db8ff; outline-offset: 2px; }
const ob = document.querySelector('.ovf button');
ob.addEventListener('click', () => { const o = document.querySelector('.ovf').classList.toggle('open'); ob.setAttribute('aria-expanded', String(o)); ob.textContent = o ? 'Show less' : '+4 more'; });

About these CSS Tags and Chips

Link, button or plain text

Tags that lead to a page are links. Chips that filter or toggle are buttons with aria-pressed, or radios when only one can be chosen. Labels that only describe something are plain text.

Colour plus words

Colour-coded labels need distinct text. color-mix() derives a tint and border from one colour per label, keeping a family of labels consistent.

Tag inputs

Turn text into a tag on Enter or comma, remove the last tag with Backspace on an empty field, and give every remove button the tag's name, such as Remove CSS.

Long lists

Show the first few tags and a +4 more button with aria-expanded, rather than hiding tags permanently.

Frequently asked questions

Which demos need JavaScript?

The tag input, counted filters and the show-more toggle.

How do I submit tags with a form?

Keep a hidden input in sync with the tags, as the tag input demo does.

Chip or tag, what is the difference?

Mostly naming. Chips are usually interactive, tags usually descriptive.