# Tag

> A chip for labels and categories. Optionally a link, optionally removable.

URL: https://apogee.avaui.dev/docs/tag
Last updated: 2026-10-15

A chip for labels and categories. Optionally a link, optionally removable.

Last updated October 15, 2026

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).

```astro
<Tag>Design</Tag>
<Tag removable value="engineering">Engineering</Tag>
<Tag href="#">Docs</Tag>
```

## Add

```sh
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

```astro
---
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

```astro
<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>
```

```astro
<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

```astro
<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:

```astro
<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:

```astro
<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-visible` ring.
-   **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 set `dismissOnClick`).

## Examples

With an icon:

```astro
<Tag><Mail slot="start" /> Email</Tag>
```

A cosmetic list with no backing state, self-removing on click:

```astro
<Tag removable dismissOnClick>Draft</Tag>
```

Controlled removal — update your own state from the event instead of letting the DOM change on its own:

```astro
<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>` (when `href` is set) or a plain `<span>` — links get full keyboard and AT navigation.
-   The remove button is a real `<button type="button">` with an `aria-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 `value` on every removable tag — the remove event’s `detail.value` is 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 `Alert` with a close action.
-   **Don’t** reach for `dismissOnClick` on tags backed by real data — handle `apogee:tag-remove` and 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.

## Related

[

BadgeA small status label — for counts, tags, and state indicators.

](https://apogee.avaui.dev/docs/badge)[

TagInputType and press Enter to add removable tags. Submits as repeated hidden inputs.

](https://apogee.avaui.dev/docs/tag-input)[

DismissibleWrap anything with a close button, with an optional auto-dismiss timeout.

](https://apogee.avaui.dev/docs/dismissible)
