EmTag
A compact label for statuses and filters — a plain span by default, and a real toggle button the moment you make it interactive.
Usage
EmTag has four props and one slot, and the slot is the label. variant picks the palette — neutral for plain categorization, success, danger and info when the tag carries a status. The status variants tint the border; neutral keeps the default one; and every variant holds a matching background in reserve for the selected state.
The prop that changes what the component is rather than how it looks is interactive. Off, the tag is static text in a pill: nothing to click, nothing to focus. On, it renders a native button with pressed-state semantics, which is the shape a filter chip should have. selected and disabled apply to both modes, but only the interactive tag communicates them to assistive technology — the details are under Accessibility.
Anatomy
<script setup lang="ts">
import { EmTag } from '@paper/emerald'
</script>
<template>
<EmTag />
</template>Composed on v0
EmTag renders a single Atom — v0’s polymorphic foundation element — and the component’s real job is deciding what to ask that Atom to be. With interactive off it renders as a span; on, it renders as a button with type="button", so a tag inside a form never submits it by accident.
The Atom’s as and renderless escape hatches are not part of the prop surface — the element choice above is the component, and exposing as would let a caller undo it. What does pass through is everything Vue’s attribute fallthrough carries: the Atom is the single root, so a @click listener, an id, or an extra class lands on whichever element the tag rendered as.
Styling hangs entirely off data attributes the template writes — data-variant for the palette, data-selected and data-disabled for state. There are no state classes, and aria-pressed and the native disabled attribute are written only in interactive mode, where there is a control for them to describe.
Examples
Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'neutral' | 'success' | 'danger' | 'info' | 'neutral' | Palette — status variants tint the border; every variant tints the background once selected |
selected | boolean | false | Selected styling via data-selected; reflected as aria-pressed when interactive |
disabled | boolean | false | Dims the tag and blocks pointer interaction. Sets the native disabled attribute only when interactive |
interactive | boolean | false | Renders a native button with type="button" instead of a span |
The default slot is the label. There are no named slots, no events, and no exposed methods — a click on an interactive tag is handled with a plain @click via attribute fallthrough.
Accessibility
The interactive prop is an accessibility decision, not a styling one — it changes the rendered element and everything assistive technology is told:
| Mode | Rendered as | Focusable | Semantics |
|---|---|---|---|
| Static | span | No | Plain text. selected and disabled are visual only |
| Interactive | button type="button" | Yes | Toggle button — aria-pressed mirrors selected; disabled is the native attribute |
An interactive tag always carries aria-pressed, true or false, so it always announces as a toggle. Use interactive only when the tag genuinely flips state in place — a filter, a selectable topic. A tag that navigates somewhere should be a link styled to match, and a tag that only displays a status should stay static; a “toggle button” that does not toggle is a promise the interface breaks.
The static tag makes the inverse promise: it tells assistive technology nothing beyond its text. selected and disabled on a static tag are stylesheet state — there is no role and no ARIA to carry them — so any meaning they express visually must also live in the text or the surrounding content.
Variant color is in the same category. success and danger differ only by border and background tint, so let the label say what the color implies — “Failed” on a danger tag, not a bare glyph the color alone must explain.
Focus handling comes from the platform: only the interactive tag can take focus, and Emerald’s focus ring appears on :focus-visible only, so keyboard focus shows a ring where a mouse click does not.