Tag
A chip for labels and categories. Optionally a link, optionally removable.
Last updated
Uses JavaScript
A document-level click listener wires every removable tag’s remove
button: clicking it dispatches a cancelable apogee:tag-remove event
(detail: { value, tag }) on the tag. Listen for it to update your own
state; set dismissOnClick to also remove the element from the DOM when
the event isn’t cancelled.
Without JS: The remove button is hidden entirely — tags render as static, non-removable chips (a link tag still works, since links need no JS).
Show codeHide code
<Tag>Design</Tag>
<Tag removable value="engineering">Engineering</Tag>
<Tag href="#">Docs</Tag>Add
npx @ava-ui/apogee add tag
No Apogee component dependencies. Depends on @lucide/astro (renders the
remove icon). Ships one delegated, document-level JS listener (progressive
enhancement).
Usage
---
import Tag from '@/components/apogee/Tag/Tag.astro'
---
<Tag removable value="engineering">Engineering</Tag>
API
@composable — exposed to Content Editors in AVA. Any other attribute
passes through to the root <span data-apogee-tag>.
| Prop | Type | Default | Description |
|---|---|---|---|
color |
'primary' | 'neutral' | 'critical' | 'positive' | 'warning' |
'neutral' |
The hue. |
variant |
'solid' | 'faded' | 'outline' |
'outline' |
The fill style. |
size |
'small' | 'medium' | 'large' |
'medium' |
Height: 24 / 28 / 32px. |
rounded |
boolean |
false |
Fully round ends. |
href |
string |
— | Renders the inner label as an <a> instead of a <span>. |
removable |
boolean |
false |
Shows a remove button (hidden without JS). |
count |
number |
— | A count pill after the label (tabular numbers). |
selectable |
boolean |
false |
A native checkbox inside the chip: click or Space toggles it, the form posts name=value. Ignored with href. |
checked |
boolean |
false |
Initial state of a selectable tag. |
name |
string |
— | Form field name of a selectable tag. |
value |
string |
— | Identifies this tag in the apogee:tag-remove event’s detail. |
dismissOnClick |
boolean |
false |
Also removes the element from the DOM when the remove event isn’t cancelled — for cosmetic lists with no backing state. |
removeLabel |
string |
'Remove' |
Accessible label for the remove button. |
class |
string |
— | Extra classes, merged last. |
Variants × colors
Show codeHide code
<Tag>Plain</Tag>
<Tag href="#">Link</Tag>
<Tag removable value="removable">Removable</Tag>
<Tag rounded>Rounded</Tag>
<Tag size="small">Small</Tag>
<Tag size="large">Large</Tag>Show codeHide code
<Tag color="primary" variant="solid">Primary</Tag>
<Tag color="neutral" variant="solid">Neutral</Tag>
<Tag color="critical" variant="solid">Critical</Tag>
<Tag color="positive" variant="solid">Positive</Tag>
<Tag color="warning" variant="solid">Warning</Tag>
<Tag color="primary" variant="faded">Primary</Tag>
<Tag color="neutral" variant="faded">Neutral</Tag>
<Tag color="critical" variant="faded">Critical</Tag>
<Tag color="positive" variant="faded">Positive</Tag>
<Tag color="warning" variant="faded">Warning</Tag>
<Tag color="primary">Primary</Tag>
<Tag color="neutral">Neutral</Tag>
<Tag color="critical">Critical</Tag>
<Tag color="positive">Positive</Tag>
<Tag color="warning">Warning</Tag>Count and selectable
Show codeHide code
<Tag count={12}>Open</Tag>
<Tag color="primary" variant="solid" count={3}>Drafts</Tag>Selectable tags are checkboxes; the chip takes the hue’s faded fill while checked:
Show codeHide code
<form class="flex flex-wrap gap-2">
<Tag selectable name="topics" value="design">Design</Tag>
<Tag selectable name="topics" value="code" checked>Code</Tag>
<Tag selectable name="topics" value="ops" color="positive">Ops</Tag>
</form>Padding
The side that holds an icon or the remove button pads less, so the glyph sits optically inside the chip:
Show codeHide code
<Tag>Plain</Tag>
<Tag><Mail slot="start" />With icon</Tag>
<Tag removable>Removable</Tag>States
- Hover / focus — the label (link or plain text) and the remove button
each get a visible
focus-visiblering. - No JS — the remove button doesn’t render at all (
no-js:hidden). - Removed — clicking remove dispatches
apogee:tag-remove; the tag only leaves the DOM if your handler calls.remove()(or you setdismissOnClick).
Examples
With an icon:
Show codeHide code
<Tag><Mail slot="start" /> Email</Tag>A cosmetic list with no backing state, self-removing on click:
<Tag removable dismissOnClick>Draft</Tag>
Controlled removal — update your own state from the event instead of letting the DOM change on its own:
<script>
document.addEventListener('apogee:tag-remove', (e) => {
const { value } = (e as CustomEvent).detail
// remove `value` from your state/store, then re-render
})
</script>
Accessibility
| Key | Action |
|---|---|
Tab |
Moves to the link (with href), the checkbox (with selectable) and the remove button, in source order. |
Enter |
Follows the link or presses the remove button. |
Space |
Toggles a selectable tag or presses the remove button. |
- The label renders a real
<a>(whenhrefis set) or a plain<span>— links get full keyboard and AT navigation. - The remove button is a real
<button type="button">with anaria-label(removeLabel, default"Remove") — pass a specific one (e.g."Remove Engineering") when several tags are on the page. - No JS — the remove button is hidden entirely rather than shown
non-functional; a link tag (
href) still works with JS off.
Tokens
| Token | Used for |
|---|---|
--border-neutral |
Tag border |
--background-elevation-raised / --foreground-neutral |
Tag background / text |
--foreground-neutral-faded / --foreground-neutral |
Remove icon default / hover color |
--outline-color-focus |
Focus ring (label + remove button) |
Do / Don’t
- Do pass
valueon every removable tag — the remove event’sdetail.valueis how you know which one to drop from state. - Do write a specific
removeLabel(“Remove Engineering”) when several tags share a page. - Don’t use Tag for a single dismissible message — that’s an
Alertwith a close action. - Don’t reach for
dismissOnClickon tags backed by real data — handleapogee:tag-removeand re-render from state instead.
Content
- Tag text is a short noun or noun phrase (one to three words), sentence case, no ending punctuation.
- Match the label wording to the data it represents exactly — a tag is often a filter or category key, not free copy.