Tag
A small label for category or status — ghost, filled, or outlined. Metadata, not interactive filters.
Variants: ghostfilledoutlined (default: filled)
Sizes: xssm (default: s)
Editorial
<!-- F12c editorial — non-derivable only. Review: Karen. -->
## Ejemplos
Category metadata:
```tsx import { Tag } from '@/components/atoms/Tag';
<Tag variant="filled" intent="neutral" size="s">Design system</Tag> <Tag variant="outline" intent="success" size="s" dot>Stable</Tag> ```
## Accesibilidad
- Tags are labels, not buttons — do not attach click handlers without turning them into real controls with roles and keyboard support. - Color/`intent` must not be the only cue; keep the text label meaningful.
### Correcto
- Tag es un `<span>` no interactivo — correcto para metadata y status display - Avatar img tiene alt='' (decorativo) — el texto del label es lo accesible - Dot usa currentColor — cambia con el intent automaticamente - Ellipsis con max-width 200px previene overflow en layouts
### Evitar
- No usar Tag para acciones — usar Chip (con onClose) o Button - No depender solo del color del intent para comunicar estado — acompanar con texto descriptivo - No usar intent='ai' para contenido no generado por AI — es un indicador semantico - No pasar dot + icon + avatar juntos — elegir uno
## Cuándo no usar
- Dismissible / selectable filters → `Chip` (interactive). - Numeric counts on icons → `Badge`.
## Criterio de uso
- Usa Tag para clasificar o describir contenido; el texto debe seguir siendo útil si se quita el color y el dot. - Reserva `success`, `warning` y `danger` para estados con significado real; `brand` y `ai` deben funcionar como énfasis, no como semántica inventada. - Mantén las etiquetas cortas. Si el usuario necesita seleccionar, quitar o cambiar el valor, usa `Chip` o un control explícito.
## Gotchas
- Tag no es interactivo por defecto: no lo conviertas en botón sólo con un click handler. - No uses un Tag como banner de alerta; para mensajes que requieren atención usa un patrón de feedback. - **Nota**: CSS autocontenido. Tag no tiene hover, transiciones ni animaciones — es puramente visual. Los 24 combos de color (3 variantes x 8 intents) estan hardcoded.
Uso
import { Tag } from '@/components/atoms/Tag';
<Tag variant="filled" intent="success" size="s">
Live
</Tag>Props
| Prop | Tipo | Default | Rango / opciones | What | How |
|---|---|---|---|---|---|
| variant | select | filled | `ghost`, `filled`, `outlined` | Surface treatment of the status tag. | filled for primary status; outlined for quiet meta; ghost for inline. Default: filled. |
| intent | select | neutral | `success`, `warning`, `danger`, `info`, `neutral`, `brand`, `ai`, `disabled` | Semantic color of the tag. | neutral for meta; success/warning/danger for state; brand/ai sparingly. Default: neutral. |
| size | select | s | `xs`, `s`, `m` | Type and padding scale. | s default; xs for dense tables; m for marketing. Default: s. |
| dot | boolean | false | `true` / `false` | Shows a leading status dot. | true for live/status lists; false for pure labels. Default: false. |
Gotchas
- a11y
Tags are not interactive by default — do not use them as buttons; use Chip or Button.
Anatomía CSS
<div class="text-gradient"> </div>
| Clase | Propósito |
|---|---|
text-gradient | root |
Tokens resueltos
Valores finales tras seguir la cadena de tokens. Derivados del source: si un token cambia, esta tabla cambia sola.
| Variante | Prop | Valor | Token |
|---|---|---|---|
| all | border | var(--stroke-thin) solid transparent | (literal) |
| gradient | fg | #0a0a0a | foreground |
| gradient | border | transparent | (literal) |
| inverse | bg | #0a0a0a | foreground |
| inverse | fg | #fafafa | background |
| ghost | fg | #8023ff | ai.text |
| ghost | disabled-fg | #525252 | muted.foreground |
| filled | bg | #8023ff | ai |
| filled | fg | #8023ff | ai.text |
| filled | disabled-bg | #f5f5f5 | muted |
| filled | disabled-fg | #525252 | muted.foreground |
| outlined | fg | #8023ff | ai.text |
| outlined | border | #8023ff | ai.text |
| outlined | disabled-fg | #525252 | muted.foreground |
| outlined | disabled-border | #f5f5f5 | muted |
| all | bg | currentColor | (literal) |
CSS mínimo funcional
Autocontenido: sin imports ni tokens. Para previews y prototipos — en producción se consume el CSS del DS (@atom-uikit/css/components.css</code>, <code>@atom-uikit/tokens/tokens.css).
/* -------------------------------------------------------------------------
Tag
Status pill for categories, labels, and section headings.
Pill shape (border-radius: 1000px). Non-interactive.
Variants: ghost (no bg), filled (bg), outlined (border)
Intents: success, warning, danger, info, neutral, brand, ai, disabled
Sizes: xs (16px), s (20px), m (24px)
Features: hasDots, hasIcon, hasAvatar
------------------------------------------------------------------------- */
.tag {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 4px;
max-width: 12.5rem;
border-radius: 9999px;
border: 1.5px solid transparent;
font-family: 'inter tight', -apple-system, blinkmacsystemfont, 'segoe ui', roboto, helvetica, arial, sans-serif;
font-weight: 500;
line-height: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* ---- Sizes ---- */
.tag--xs { height: 1rem; padding: 0 8px; font-size: 10.24px; }
.tag--s { height: 1.25rem; padding: 0 8px; font-size: 10.24px; }
.tag--m { height: 1.5rem; padding: 0 8px; font-size: 12.8px; }
/* ---- l: eyebrow de hero ----
El unico tamano pensado para FRASE, no para etiqueta de estado: por eso
levanta el max-width de 12.5rem y el nowrap del bloque base. Sin esto una
frase como "Inteligencia de Conversion para WhatsApp" se corta con ellipsis. */
.tag--l {
height: auto;
max-width: none;
padding: 8px 16px;
font-size: 10.24px;
text-transform: uppercase;
letter-spacing: 0.08em;
font-weight: 600;
white-space: normal;
overflow: visible;
}
/* ---- Mono (patron ATOM: etiqueta tecnica) ---- */
/* Tipografia mono en caja recta. Combina con --inverse o cualquier intent. */
.tag--mono {
font-family: 'interval', ui-monospace, 'cascadia code', monospace;
font-weight: 400;
font-size: 10.24px;
text-transform: uppercase;
letter-spacing: 0.06em;
border-radius: 4px;
}
/* ---- Gradient (borde degradado de marca) ----
Highlight tipografico del sistema: el mismo --gradient-highlight del
.text-gradient, aqui como CONTORNO. El borde degradado se hace con dos capas
de background y distintos clip boxes — un border-image no permite radio, y
este pill es radius-full. Por eso el border va transparent: es la ventana por
la que se ve la capa de gradiente. */
.tag--gradient {
border-color: transparent;
background-image:
linear-gradient(#fafafa, #fafafa),
var(--gradient-highlight);
background-origin: border-box;
background-clip: padding-box, border-box;
color: #0a0a0a;
}
/* ---- Inverse (relleno con los semantics del contexto: flipa en dark) ---- */
.tag--inverse {
color: #fafafa;
background-color: #0a0a0a;
}
/* ---- Ghost (text only, no bg) ---- */
.tag--ghost.tag--success { color: #0a290f; }
.tag--ghost.tag--warning { color: #973c00; }
.tag--ghost.tag--danger { color: #c21e12; }
.tag--ghost.tag--info { color: #0e7490; }
.tag--ghost.tag--neutral { color: #0a0a0a; }
.tag--ghost.tag--brand { color: #a44200; }
.tag--ghost.tag--ai { color: #8023ff; }
.tag--ghost.tag--disabled { color: #525252; }
/* ---- Filled (bg + text) ---- */
.tag--filled.tag--success {
background-color: color-mix(in srgb, #25d366 18%, #fafafa);
color: #0a290f;
}
.tag--filled.tag--warning {
background-color: color-mix(in srgb, #fe9900 18%, #fafafa);
color: #973c00;
}
.tag--filled.tag--danger {
background-color: color-mix(in srgb, #f84131 16%, #fafafa);
color: #c21e12;
}
.tag--filled.tag--info {
background-color: color-mix(in srgb, #22d3ee 18%, #fafafa);
color: #0e7490;
}
.tag--filled.tag--neutral { background-color: #f5f5f5; color: #0a0a0a; }
.tag--filled.tag--brand {
background-color: color-mix(in srgb, #ff6600 18%, #fafafa);
color: #a44200;
}
/* El stop de arranque era --success: mientras ai no tuvo superficie propia, el
intent se pintaba de verde y no se distinguia del de exito. Ahora arranca en
su propio violeta. */
.tag--filled.tag--ai {
background: linear-gradient(
136deg,
color-mix(in srgb, #8023ff 20%, #fafafa),
color-mix(in srgb, #8023ff 12%, #fafafa)
);
color: #8023ff;
}
.tag--filled.tag--disabled { background-color: #f5f5f5; color: #525252; }
/* ---- Outlined (border + text) ---- */
.tag--outlined.tag--success { border-color: #0a290f; color: #0a290f; }
.tag--outlined.tag--warning { border-color: #973c00; color: #973c00; }
.tag--outlined.tag--danger { border-color: #c21e12; color: #c21e12; }
.tag--outlined.tag--info { border-color: #0e7490; color: #0e7490; }
.tag--outlined.tag--neutral { border-color: #e5e5e5; color: #0a0a0a; }
.tag--outlined.tag--brand { border-color: #a44200; color: #a44200; }
.tag--outlined.tag--ai { border-color: #8023ff; color: #8023ff; }
.tag--outlined.tag--disabled { border-color: #f5f5f5; color: #525252; }
/* ---- Parts ---- */
.tag__dot {
width: 0.375rem;
height: 0.375rem;
border-radius: 9999px;
background-color: currentColor;
flex-shrink: 0;
}
.tag__icon {
display: flex;
align-items: center;
flex-shrink: 0;
}
.tag__avatar {
flex-shrink: 0;
}
.tag__label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 1px;
}
Codigo fuente
import { type ReactNode } from 'react'; type TagVariant = 'ghost' | 'filled' | 'outlined'; type TagIntent = 'success' | 'warning' | 'danger' | 'info' | 'neutral' | 'brand' | 'ai' | 'disabled'; type TagSize = 'xs' | 's' | 'm'; export type TagProps = { variant?: TagVariant; intent?: TagIntent; size?: TagSize; dot?: boolean; icon?: ReactNode; avatar?: string; children: string; className?: string; }; function cn(...classes: (string | false | undefined | null)[]) { return classes.filter(Boolean).join(' '); } export function Tag({ variant = 'filled', intent = 'neutral', size = 's', dot = false, icon, avatar, children, className, }: TagProps) { const classes = cn( 'tag', `tag--${variant}`, `tag--${intent}`, `tag--${size}`, className, ); return ( <span className={classes}> {dot && <span className="tag__dot" />} {avatar && ( <span className="tag__avatar"> <img src={avatar} alt="" /> </span> )} {icon && <span className="tag__icon">{icon}</span>} <span className="tag__label">{children}</span> </span> ); }
Webflow
Pega en el Designer como application/json, luego convierte a Component (Atom / Tag) y publica. Formato interno no documentado de Webflow — regenerable, no dependencia de runtime.
Setup del sitio (una vez)
Custom Code → Head:
<link rel="stylesheet" href="https://atom-web-ds.vercel.app/v1/tokens.css"> <link rel="stylesheet" href="https://atom-web-ds.vercel.app/v1/components.css">
Unsupported (no silencioso)
selectoron.tag--ghost.tag--success— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)selectoron.tag--ghost.tag--warning— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)selectoron.tag--ghost.tag--danger— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)selectoron.tag--ghost.tag--info— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)selectoron.tag--ghost.tag--neutral— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)selectoron.tag--ghost.tag--brand— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)selectoron.tag--ghost.tag--ai— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)selectoron.tag--ghost.tag--disabled— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)
Tras pegar: Create component → nombre Atom / Tag → Publish.