Tag <l-tag>
Tags are compact chips for tokens, filters, and selected values, with an optional remove button.
<l-tag removable>Design</l-tag><l-tag>Options
Removable
Add removable for a × button. The user can also press Backspace or Delete while it is focused. Each removal fires a cancelable remove event; if nothing calls preventDefault(), the tag removes itself from the DOM.
Code
<div class="flex flex-wrap items-center gap-2">
<l-tag>Design</l-tag>
<l-tag>Development</l-tag>
<l-tag removable>Removable</l-tag>
</div>Sizes
Add size="sm" or size="lg". Default is md. A removable tag is always at least 24px tall to keep its remove target accessible.
Code
<div class="flex flex-wrap items-center gap-2">
<l-tag
size="sm"
removable
>Small</l-tag
>
<l-tag removable>Medium</l-tag>
<l-tag
size="lg"
removable
>Large</l-tag
>
</div>Leading and trailing content
Put an icon or avatar in the prefix slot, and a count or trailing glyph in the suffix slot. Both get the chip's own gutter, so they need no margin.
Code
<div class="flex flex-wrap items-center gap-2">
<l-tag removable>
<l-icon
slot="prefix"
name="mdi:tag-outline"
></l-icon>
Marketing
</l-tag>
<l-tag removable>
<l-icon
slot="prefix"
name="mdi:account-outline"
></l-icon>
Jane Cooper
</l-tag>
</div>Selectable
Add selectable to turn the chip into a filter control. The tag becomes a toggle button, and re-activating a selected tag deselects it — so a facet always has a way back to "all". Each toggle fires a change event carrying the new selected state.
Code
<div class="flex flex-wrap items-center gap-2">
<l-tag
selectable
selected
>Any time</l-tag
>
<l-tag selectable>Today</l-tag>
<l-tag selectable>This week</l-tag>
<l-tag selectable>This month</l-tag>
</div>Checkbox
Add control="checkbox" for a multi-select facet: the library's checkbox rides inside and the chip becomes its label, so clicking anywhere on the chip toggles it. Implies selectable.
Code
<div class="flex flex-wrap items-center gap-2">
<l-tag
selectable
control="checkbox"
>In stock <span slot="suffix">128</span></l-tag
>
<l-tag
selectable
control="checkbox"
selected
>On sale <span slot="suffix">42</span></l-tag
>
<l-tag
selectable
control="checkbox"
>New arrivals <span slot="suffix">17</span></l-tag
>
</div>Disabled
Add disabled to block removal. The label stays legible — disabled is conveyed by the dimmed × button and the not-allowed cursor, not by washing out the text.
Code
<div class="flex flex-wrap items-center gap-2">
<l-tag
removable
disabled
>Locked</l-tag
>
</div>Examples
Filter panel
One tag per facet value: a checkbox for a multi-select axis, the count in the suffix slot. Listen for change on the container — the event bubbles and carries selected.
Code
<div class="flex max-w-md flex-col gap-4">
<div class="flex flex-col gap-2">
<span class="text-xs font-medium text-[var(--l-color-text-secondary)]">Category</span>
<div class="flex flex-wrap gap-2">
<l-tag
selectable
control="checkbox"
>
<l-icon
slot="prefix"
name="mdi:camera-outline"
></l-icon>
Cameras <span slot="suffix">128</span>
</l-tag>
<l-tag
selectable
control="checkbox"
selected
>
<l-icon
slot="prefix"
name="mdi:circle-outline"
></l-icon>
Lenses <span slot="suffix">54</span>
</l-tag>
<l-tag
selectable
control="checkbox"
>
<l-icon
slot="prefix"
name="mdi:tripod"
></l-icon>
Tripods <span slot="suffix">12</span>
</l-tag>
</div>
</div>
<div class="flex flex-col gap-2">
<span class="text-xs font-medium text-[var(--l-color-text-secondary)]">Brand</span>
<div class="flex flex-wrap gap-2">
<l-tag
selectable
control="checkbox"
>Acme <span slot="suffix">86</span></l-tag
>
<l-tag
selectable
control="checkbox"
>Globex <span slot="suffix">41</span></l-tag
>
<l-tag
selectable
control="checkbox"
>Initech <span slot="suffix">23</span></l-tag
>
<l-tag
selectable
control="checkbox"
>Umbrella <span slot="suffix">12</span></l-tag
>
<l-tag
selectable
control="checkbox"
>Vandelay <span slot="suffix">9</span></l-tag
>
</div>
</div>
</div>Theming the selected state
--selected-color sets the text, border, and checkbox accent at once, and the background is derived from it. Add --selected-background for a different fill, and --border-radius to trade the pill for a softer rectangle. Every custom property inherits, so setting them on the group themes each chip inside.
A dense filter drawer usually wants a step between md and lg: --height and --font-size (and --padding-inline if needed) land anywhere between them, so ::part(base) stays out of it. The example below sits at 26px / 13px.
Pair a semantic text token with its matching -soft fill — those two are designed to clear 4.5:1 together in both light and dark. A --selected-color on its own must clear that bar against the tint the chip derives from it.
Code
<div
class="flex flex-wrap items-center gap-2 [--border-radius:var(--l-radius-md)] [--font-size:13px] [--height:26px] [--selected-background:var(--l-color-bg-fill-success-soft)] [--selected-color:var(--l-color-text-success)]"
>
<l-tag
selectable
control="checkbox"
selected
>Delivered <span slot="suffix">128</span></l-tag
>
<l-tag
selectable
control="checkbox"
>Pending <span slot="suffix">42</span></l-tag
>
<l-tag
selectable
control="checkbox"
>Cancelled <span slot="suffix">7</span></l-tag
>
</div>Accessibility
Criteria
- Target size
The remove button keeps a minimum 24×24px hit target, and a selectable chip is taller than a display one
WCAG2.5.8RGAA13.10- Keyboard
The remove button is reachable with Tab and removes the tag with Backspace / Delete; a selectable tag toggles with Enter or Space
WCAG2.1.1RGAA12.9- State
A selectable tag exposes
aria-pressed, or the checked state of its checkbox withcontrol=checkboxWCAG4.1.2RGAA7.1
Keyboard interactions
Selectors & testing
Selection state lives on the reflected selected attribute of the host — that is the one to query. aria-pressed sits on the toggle button and control="checkbox" renders its <input>, both inside the shadow DOM, so a descendant selector from the light DOM never matches them. Role queries do work: the accessibility tree is unaffected by the shadow boundary.
document.querySelectorAll('l-tag[selected]'); // ✅ reflected boolean attribute
screen.getByRole('button', { pressed: true }); // ✅ selectable tag
screen.getByRole('checkbox', { checked: true }); // ✅ control="checkbox"
tag.querySelector('input'); // ❌ null — the checkbox is in the shadow root
tag.getAttribute('aria-pressed'); // ❌ null — it is on the inner buttonActivation goes through the host, so a test runner clicks the chip it resolved by data-testid — no reaching into the shadow root, and no custom command.
tag.click(); // ✅ toggles a selectable chip (does not remove a removable one)/* Style by the reflected attribute; ::part() reaches the inner nodes. */
l-tag[selected]::part(base) {
outline: 1px dashed var(--l-color-border);
}Controlled usage
The chip is uncontrolled: it applies the new state, then fires change. To veto a toggle — a "max 3 filters" rule, an async guard — set selected back in the listener. The revert lands in the same render, so nothing is painted in between.
panel.addEventListener('change', (event) => {
const tag = event.target;
if (tag.selected && selectedCount() > 3) tag.selected = false;
});API reference
Importing
import 'luxen-ui/tag';Attributes & Properties
sizeTagSizedefault:'md'Property- Tag size:
sm,md(default), orlg. removablebooleandefault:falseProperty- Show a remove button (and enable Backspace/Delete removal).
selectablebooleandefault:falseProperty- Make the tag a filter control the user can toggle on and off.
selectedbooleandefault:falseProperty- Whether the tag is selected. Reflected, so
[selected]is styleable. controlTagControldefault:'none'Property- What drives the selection:
none(default) makes the chip itself a toggle button;checkboxrenders a checkbox inside and makes the chip its label — the right choice for a multi-select facet. Impliesselectable. disabledbooleandefault:falseProperty- Disable the tag — dims it and blocks selection and removal.
Events
removecancelableEvent- Fired when the user removes the tag (× click or Backspace/Delete). Cancelable; if not prevented the tag removes itself. Not composed, does not bubble.
changeEvent- Fired when a
selectabletag is toggled. Not cancelable — like the platform's ownchange, and likel-segmented-control. Bubbles. Properties:selected: boolean. The chip is uncontrolled: it applies the new state before dispatching, so a host that vetoes a toggle (a "max 3 filters" rule, an async guard) setsselectedback in the listener — the revert lands in the same render and is never painted.
Slots
(default)Slot- The tag label.
prefixSlot- Leading content, e.g. an
<l-icon>or<l-avatar>. suffixSlot- Trailing content, e.g. a result count. Gets the chip's own gutter, so no margin is needed.
CSS parts
basePart- The chip container.
contentPart- The label wrapper.
togglePart- The toggle button rendered by
selectable(not withcontrol="checkbox"). checkboxPart- The native checkbox rendered by
control="checkbox". removePart- The remove button.
CSS custom properties
--border-radiusCustom property- Corner radius. Defaults to a full pill.
--heightCustom property- Chip height. Defaults to the
sizestep (a selectable chip is taller, to keep a comfortable target). --font-sizeCustom property- Label size. Defaults to the
sizestep — 12px, or 14px atsize="lg". Set it with--heightto land between the two steps in a dense filter panel. --padding-inlineCustom property- Horizontal padding. Defaults to the
sizestep. --backgroundCustom property- Chip background.
--colorCustom property- Text color.
--selected-colorCustom property- Text, border, and checkbox accent when selected. Defaults to the library's form-control accent, lightened in dark mode.
--selected-backgroundCustom property- Chip background when selected. Defaults to a tint of
--selected-color.