Tag

An interactive chip for filters and selections — a bordered button or plain text, with optional checkbox, dot, flag, avatar, count and remove button.

Examples

Styles

Prominent (bordered, the default) and Subtle (text only). Tags are buttons — a filter toggle exposes its state with aria-pressed, which also drives the selected look.

<div style="display:flex;flex-wrap:wrap;align-items:center;gap:0.75rem">
  <button type="button" class="kompan-tag">Label</button>
  <button type="button" class="kompan-tag" aria-pressed="true">Label</button>
  <button type="button" class="kompan-tag kompan-tag_subtle">Label</button>
</div>

Sizes

sm (default, 24px), md (24px, 14px text) and lg (28px). Checkbox, close and count scale with the tag.

<div style="display:flex;flex-wrap:wrap;align-items:center;gap:0.75rem">
  <button type="button" class="kompan-tag kompan-tag_sm">Small</button>
  <button type="button" class="kompan-tag kompan-tag_md">Medium</button>
  <button type="button" class="kompan-tag kompan-tag_lg">Large</button>
  <span class="kompan-tag kompan-tag_sm">Small<button type="button" class="kompan-tag__close" aria-label="Remove Small"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 3 3 9M3 3l6 6"/></svg></button></span>
  <span class="kompan-tag kompan-tag_md">Medium<button type="button" class="kompan-tag__close" aria-label="Remove Medium"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 3 3 9M3 3l6 6"/></svg></button></span>
  <span class="kompan-tag kompan-tag_lg">Large<button type="button" class="kompan-tag__close" aria-label="Remove Large"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 3 3 9M3 3l6 6"/></svg></button></span>
</div>

With checkbox

For multi-select filters the tag is a label wrapping a real checkbox; checking it selects the whole tag.

<div style="display:flex;flex-wrap:wrap;align-items:center;gap:0.75rem">
  <label class="kompan-tag"><input type="checkbox" class="kompan-tag__checkbox">Label</label>
  <label class="kompan-tag"><input type="checkbox" class="kompan-tag__checkbox" checked>Label</label>
  <label class="kompan-tag"><input type="checkbox" class="kompan-tag__checkbox" disabled>Label</label>
  <label class="kompan-tag"><input type="checkbox" class="kompan-tag__checkbox" checked disabled>Label</label>
  <label class="kompan-tag kompan-tag_md"><input type="checkbox" class="kompan-tag__checkbox">Label</label>
  <label class="kompan-tag kompan-tag_lg"><input type="checkbox" class="kompan-tag__checkbox" checked>Label</label>
</div>

Leading content

A status dot, a flag (or any 16px icon) or a round avatar before the label. The dot colour is set with --kompan-tag-dot.

<div style="display:flex;flex-wrap:wrap;align-items:center;gap:0.75rem">
  <button type="button" class="kompan-tag"><span class="kompan-tag__dot"></span>Label</button>
  <button type="button" class="kompan-tag"><svg class="kompan-tag__icon" viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="8" fill="#d80027"/><path d="M6.5 0.1v6.4H0.1a8 8 0 0 0 0 3h6.4v6.4a8 8 0 0 0 3 0V9.5h6.4a8 8 0 0 0 0-3H9.5V0.1a8 8 0 0 0-3 0Z" fill="#f0f0f0"/></svg>Label</button>
  <button type="button" class="kompan-tag"><img class="kompan-tag__avatar" src="https://images.unsplash.com/photo-1600603406200-5b2a104684ac?w=64&h=64&fit=crop&crop=faces&q=80" alt="">Label</button>
  <button type="button" class="kompan-tag kompan-tag_subtle"><span class="kompan-tag__dot" style="--kompan-tag-dot: var(--color-utility-orange-500)"></span>Label</button>
  <button type="button" class="kompan-tag kompan-tag_subtle"><svg class="kompan-tag__icon" viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="8" fill="#d80027"/><path d="M6.5 0.1v6.4H0.1a8 8 0 0 0 0 3h6.4v6.4a8 8 0 0 0 3 0V9.5h6.4a8 8 0 0 0 0-3H9.5V0.1a8 8 0 0 0-3 0Z" fill="#f0f0f0"/></svg>Label</button>
  <button type="button" class="kompan-tag kompan-tag_subtle"><img class="kompan-tag__avatar" src="https://images.unsplash.com/photo-1600603406200-5b2a104684ac?w=64&h=64&fit=crop&crop=faces&q=80" alt="">Label</button>
</div>

Count and remove

A count pill after the label, or a remove button. A removable tag is a span holding the button — buttons cannot nest — so combine with a checkbox rather than a button tag.

<div style="display:flex;flex-wrap:wrap;align-items:center;gap:0.75rem">
  <button type="button" class="kompan-tag">Label <span class="kompan-tag__count">5</span></button>
  <span class="kompan-tag">Label<button type="button" class="kompan-tag__close" aria-label="Remove Label"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 3 3 9M3 3l6 6"/></svg></button></span>
  <span class="kompan-tag"><svg class="kompan-tag__icon" viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="8" fill="#d80027"/><path d="M6.5 0.1v6.4H0.1a8 8 0 0 0 0 3h6.4v6.4a8 8 0 0 0 3 0V9.5h6.4a8 8 0 0 0 0-3H9.5V0.1a8 8 0 0 0-3 0Z" fill="#f0f0f0"/></svg>Label<button type="button" class="kompan-tag__close" aria-label="Remove Label"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 3 3 9M3 3l6 6"/></svg></button></span>
  <label class="kompan-tag"><input type="checkbox" class="kompan-tag__checkbox" checked>Label<button type="button" class="kompan-tag__close" aria-label="Remove Label"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 3 3 9M3 3l6 6"/></svg></button></label>
  <button type="button" class="kompan-tag kompan-tag_lg"><img class="kompan-tag__avatar" src="https://images.unsplash.com/photo-1600603406200-5b2a104684ac?w=64&h=64&fit=crop&crop=faces&q=80" alt="">Label <span class="kompan-tag__count">12</span></button>
</div>

States

Default, selected, disabled and selected + disabled. Use the disabled attribute on buttons and inputs; aria-disabled on span tags.

<div style="display:flex;flex-wrap:wrap;align-items:center;gap:0.75rem">
  <button type="button" class="kompan-tag">Default</button>
  <button type="button" class="kompan-tag" aria-pressed="true">Selected</button>
  <button type="button" class="kompan-tag" disabled>Disabled</button>
  <button type="button" class="kompan-tag" aria-pressed="true" disabled>Selected</button>
  <span class="kompan-tag" aria-disabled="true">Removable<button type="button" class="kompan-tag__close" aria-label="Remove Removable"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 3 3 9M3 3l6 6"/></svg></button></span>
</div>

Colour themes

Dark and Brand tags for use on light surfaces. Figma has no selected or disabled variants for these themes.

<div style="display:flex;flex-wrap:wrap;align-items:center;gap:0.75rem">
  <button type="button" class="kompan-tag kompan-tag_dark">Label</button>
  <span class="kompan-tag kompan-tag_dark">Label<button type="button" class="kompan-tag__close" aria-label="Remove Label"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 3 3 9M3 3l6 6"/></svg></button></span>
  <button type="button" class="kompan-tag kompan-tag_brand">Label</button>
  <span class="kompan-tag kompan-tag_brand">Label<button type="button" class="kompan-tag__close" aria-label="Remove Label"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 3 3 9M3 3l6 6"/></svg></button></span>
  <button type="button" class="kompan-tag kompan-tag_brand kompan-tag_lg"><svg class="kompan-tag__icon" viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="8" fill="#d80027"/><path d="M6.5 0.1v6.4H0.1a8 8 0 0 0 0 3h6.4v6.4a8 8 0 0 0 3 0V9.5h6.4a8 8 0 0 0 0-3H9.5V0.1a8 8 0 0 0-3 0Z" fill="#f0f0f0"/></svg>Label</button>
</div>

Accessibility

Pick the element by behaviour

A tag that toggles a filter is a <button type="button"> with aria-pressed. A tag in a multi-select group is a <label> wrapping input.kompan-tag__checkbox. A tag that only displays a value (with or without a remove button) is a <span>. Never put a button.kompan-tag__close inside a button tag — nested buttons are invalid and unreachable.

Expose the selected state

The selected look follows aria-pressed="true", aria-selected="true" or a checked checkbox — prefer those over the kompan-tag_selected class so assistive technology gets the state too. Use aria-selected only inside a listbox/tab pattern.

Remove button needs a name

button.kompan-tag__close shows only an × icon. Set aria-label="Remove {label}" so the action is announced with its target, and move focus to a sensible neighbour after the tag is removed.

Disabled

Use the disabled attribute on button tags and on the checkbox; use aria-disabled="true" on span tags (it also disables the inner remove button). Disabled tags keep their visible label but drop to 10% text contrast, so do not rely on them to convey information.

Focus is visible

Tags, checkboxes and remove buttons show the 2px + 4px focus ring on :focus-visible (the ring wraps the whole tag when its checkbox is focused). Do not remove it; on non-white backgrounds override --color-bg-primary in the ring if the gap becomes invisible.

Contrast

Prominent text is 60% black on white (about 5.7:1, AA). Subtle text is 40% black (about 2.8:1) and the remove icon is 20% black — both fail AA. Treat Subtle as secondary UI or darken --kompan-tag-fg / --kompan-tag-close-fg when the tag carries essential information. The dot conveys status by colour only; say the status in the label too.

Stylesheet

Shipped as @wah-kompan/kompan-ui/components/tag.css. Every value is a token reference, so it themes with the rest of the system rather than being restyled.

css
.kompan-tag {
  /* Size ramp — sm is the Figma default (24px) */
  --kompan-tag-py: 0.1875rem;                    /* 3px, unbound in Figma */
  --kompan-tag-px: var(--spacing-md);
  --kompan-tag-gap: var(--spacing-xs);
  --kompan-tag-font-size: var(--type-font-size-xs);
  --kompan-tag-line-height: var(--type-line-height-xs);
  --kompan-tag-checkbox: 0.875rem;               /* 14px */
  --kompan-tag-close: 0.875rem;                  /* 14px box */
  --kompan-tag-close-pad: var(--spacing-xxs);    /* → 10px icon */
  --kompan-tag-count-h: 1rem;
  --kompan-tag-count-px: var(--spacing-xs);
  --kompan-tag-count-font-size: var(--type-font-size-xs);
  --kompan-tag-edge: min(0px, calc(var(--spacing-xs) - var(--kompan-tag-px)));

  /* Colour roles — Prominent / Light */
  --kompan-tag-bg: var(--color-input-bg-primary);
  --kompan-tag-fg: var(--color-input-fg-secondary);
  --kompan-tag-border: var(--color-input-border-primary);
  --kompan-tag-bg-hover: var(--color-button-secondary-subtle-bg-hover);
  --kompan-tag-close-fg: var(--color-button-secondary-subtle-fg-tertiary);
  --kompan-tag-focus-ring: 0 0 0 2px var(--color-bg-primary), 0 0 0 4px var(--color-button-focus-ring);

  display: inline-flex;
  align-items: center;
  gap: var(--kompan-tag-gap);
  box-sizing: border-box;
  margin: 0;
  padding: var(--kompan-tag-py) var(--kompan-tag-px);
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--kompan-tag-bg);
  box-shadow: inset 0 0 0 var(--stroke-sm) var(--kompan-tag-border);
  color: var(--kompan-tag-fg);
  font-family: var(--type-font-family-body);
  font-size: var(--kompan-tag-font-size);
  font-weight: var(--type-font-weight-medium);
  line-height: var(--kompan-tag-line-height);
  letter-spacing: 0;
  white-space: nowrap;
  text-decoration: none;
  cursor: default;
  user-select: none;
  transition: background-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
}

button.kompan-tag,
label.kompan-tag,
a.kompan-tag {
  appearance: none;
  cursor: pointer;
}

/* Implied hover — Figma has no hover variant; overlay so it works on any theme */
button.kompan-tag:hover,
label.kompan-tag:hover,
a.kompan-tag:hover {
  background-image: linear-gradient(var(--kompan-tag-bg-hover), var(--kompan-tag-bg-hover));
}

.kompan-tag:focus-visible,
.kompan-tag:has(.kompan-tag__checkbox:focus-visible) {
  outline: none;
  box-shadow: inset 0 0 0 var(--stroke-sm) var(--kompan-tag-border), var(--kompan-tag-focus-ring);
}

/* Sizes */
.kompan-tag_sm {
  --kompan-tag-py: 0.1875rem;
  --kompan-tag-px: var(--spacing-md);
}

.kompan-tag_md {
  --kompan-tag-py: var(--spacing-xxs);
  --kompan-tag-px: 0.5625rem;                    /* 9px, unbound in Figma */
  --kompan-tag-gap: 0.3125rem;                   /* 5px, unbound in Figma */
  --kompan-tag-font-size: var(--type-font-size-sm);
  --kompan-tag-line-height: var(--type-line-height-sm);
  --kompan-tag-checkbox: 1rem;
  --kompan-tag-close: 1rem;
  --kompan-tag-count-h: 1.125rem;
  --kompan-tag-count-px: 0.3125rem;
}

.kompan-tag_lg {
  --kompan-tag-py: var(--spacing-xs);
  --kompan-tag-px: 0.625rem;                     /* 10px, unbound in Figma */
  --kompan-tag-gap: var(--spacing-sm);
  --kompan-tag-font-size: var(--type-font-size-sm);
  --kompan-tag-line-height: var(--type-line-height-sm);
  --kompan-tag-checkbox: 1.125rem;
  --kompan-tag-close: 1.25rem;
  --kompan-tag-close-pad: 0.1875rem;             /* → 14px icon */
  --kompan-tag-count-h: 1.25rem;
  --kompan-tag-count-px: var(--spacing-sm);
  --kompan-tag-count-font-size: var(--type-font-size-sm);
}

/* Style: Subtle (Text) — no chrome, 2px hit padding */
.kompan-tag_subtle {
  --kompan-tag-py: var(--spacing-xxs);
  --kompan-tag-px: var(--spacing-xxs);
  --kompan-tag-bg: transparent;
  --kompan-tag-border: transparent;
  --kompan-tag-fg: var(--color-input-fg-tertiary);
}

/* Colour themes */
.kompan-tag_dark {
  --kompan-tag-bg: var(--color-bg-solid);
  --kompan-tag-fg: var(--color-input-fg-selected);
  --kompan-tag-border: var(--color-branch-button-tertiary-bg-hover);
  --kompan-tag-bg-hover: var(--color-branch-button-tertiary-bg-hover);
  --kompan-tag-close-fg: var(--color-button-secondary-subtle-fg-active);
}

.kompan-tag_brand {
  --kompan-tag-bg: var(--color-branch-bg-primary);
  --kompan-tag-fg: var(--color-branch-text-primary);
  --kompan-tag-bg-hover: var(--color-branch-button-tertiary-bg-hover);
  --kompan-tag-close-fg: var(--color-button-secondary-subtle-fg-active);
}

/* Selected */
.kompan-tag_selected,
.kompan-tag[aria-pressed="true"],
.kompan-tag[aria-selected="true"],
.kompan-tag:has(.kompan-tag__checkbox:checked) {
  --kompan-tag-bg: var(--color-input-bg-selected);
  --kompan-tag-fg: var(--color-input-fg-selected);
  --kompan-tag-bg-hover: var(--color-branch-button-tertiary-bg-hover);
  --kompan-tag-close-fg: var(--color-button-secondary-subtle-fg-active);
}

/* Disabled */
.kompan-tag:disabled,
.kompan-tag[aria-disabled="true"],
.kompan-tag:has(.kompan-tag__checkbox:disabled) {
  --kompan-tag-fg: var(--color-input-fg-secondary-disabled);
  --kompan-tag-border: var(--color-input-border-primary-disabled);
  --kompan-tag-bg-hover: transparent;
  cursor: not-allowed;
}

.kompan-tag_selected:disabled,
.kompan-tag_selected[aria-disabled="true"],
.kompan-tag[aria-pressed="true"]:disabled,
.kompan-tag[aria-pressed="true"][aria-disabled="true"],
.kompan-tag:has(.kompan-tag__checkbox:checked:disabled) {
  --kompan-tag-fg: var(--color-input-fg-selected);
}

.kompan-tag[aria-disabled="true"] .kompan-tag__close,
.kompan-tag:disabled .kompan-tag__close {
  pointer-events: none;
}

/* Leading: checkbox */
.kompan-tag__checkbox {
  appearance: none;
  flex: none;
  box-sizing: border-box;
  width: var(--kompan-tag-checkbox);
  height: var(--kompan-tag-checkbox);
  margin: 0;
  margin-left: var(--kompan-tag-edge);
  border: 0;
  border-radius: var(--radius-xs);
  background: var(--color-input-bg-primary) center / 72% no-repeat;
  box-shadow: inset 0 0 0 var(--stroke-sm) var(--color-input-border-primary);
  color: inherit;
  cursor: inherit;
  transition: background-color 0.15s ease, box-shadow 0.15s ease;
}

.kompan-tag__checkbox:checked {
  background-color: var(--color-input-bg-selected);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%23fff' stroke-width='1.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 6.5 5 9l4.5-6'/%3E%3C/svg%3E");
  box-shadow: inset 0 0 0 var(--stroke-sm) var(--color-input-bg-selected);
}

.kompan-tag__checkbox:disabled {
  background-color: var(--color-input-bg-disabled);
  box-shadow: inset 0 0 0 var(--stroke-sm) var(--color-input-border-primary-disabled);
}

.kompan-tag__checkbox:checked:disabled {
  background-color: var(--color-input-bg-selected);
  box-shadow: inset 0 0 0 var(--stroke-sm) var(--color-input-bg-selected);
}

.kompan-tag__checkbox:focus-visible {
  outline: none;
}

/* Leading: dot / icon / avatar */
.kompan-tag__dot {
  --kompan-tag-dot: var(--color-fg-success-secondary);
  flex: none;
  display: inline-block;
  width: 0.5rem;
  height: 0.5rem;
  margin-left: min(0px, calc(var(--spacing-sm) - var(--kompan-tag-px)));
}

.kompan-tag__dot::before {
  content: "";
  display: block;
  width: 0.375rem;
  height: 0.375rem;
  margin: 1px;
  border-radius: 50%;
  background: var(--kompan-tag-dot);
}

.kompan-tag__icon,
.kompan-tag__avatar {
  flex: none;
  display: block;
  width: var(--width-icon-sm);
  height: var(--width-icon-sm);
  margin-left: var(--kompan-tag-edge);
}

.kompan-tag__avatar {
  border-radius: var(--radius-full);
  object-fit: cover;
  box-shadow: inset 0 0 0 var(--stroke-xs) var(--color-border-tertiary);
}

/* Trailing: count */
.kompan-tag__count {
  flex: none;
  display: inline-block;
  box-sizing: border-box;
  min-width: var(--kompan-tag-count-h);
  padding: 0 var(--kompan-tag-count-px);
  margin-right: var(--kompan-tag-edge);
  border-radius: 0.1875rem;                      /* 3px, unbound in Figma */
  background: var(--color-input-bg-secondary);
  color: var(--color-input-fg-secondary);
  font-size: var(--kompan-tag-count-font-size);
  line-height: var(--kompan-tag-count-h);
  text-align: center;
}

/* Trailing: remove button */
.kompan-tag__close {
  appearance: none;
  flex: none;
  display: inline-flex;
  box-sizing: border-box;
  width: var(--kompan-tag-close);
  height: var(--kompan-tag-close);
  padding: var(--kompan-tag-close-pad);
  margin: 0;
  margin-right: var(--kompan-tag-edge);
  border: 0;
  border-radius: 0.1875rem;                      /* 3px, unbound in Figma */
  background: transparent;
  color: var(--kompan-tag-close-fg);
  cursor: pointer;
  transition: color 0.15s ease, background-color 0.15s ease;
}

.kompan-tag__close svg {
  display: block;
  width: 100%;
  height: 100%;
}

.kompan-tag__close:hover {
  color: var(--kompan-tag-fg);
  background: var(--kompan-tag-bg-hover);
}

.kompan-tag__close:focus-visible {
  outline: none;
  box-shadow: var(--kompan-tag-focus-ring);
}