Chip
A compact pill for a filter or selection you can remove. Multi-select tags and active filters.
Sizes: xssmlxl (default: s)
Editorial
<!-- F12c editorial — non-derivable only. Review: Karen. -->
## Ejemplos
Selected filter chip:
```tsx import { Chip } from '@/components/atoms/Chip';
<Chip type="filled" size="s" onClose={() => {}}> Design </Chip> ```
## Accesibilidad
- `onClose` must have an accessible name (`aria-label` on the close control). - The visual prop is named `type` (not `variant`) — keep labels in sync with registry.
### Correcto
- Close button tiene aria-label='Remove {text}' automaticamente - Close button usa `tabIndex={-1}` — no focusable por Tab (el chip completo recibe foco) - focus-visible y focused prop muestran focus ring para teclado - disabled desactiva tanto el chip como el close button
### Evitar
- No usar Chip para status display estatico — usar Tag en su lugar - No pasar JSX como children — solo string (necesario para aria-label del close) - No omitir onClose si el chip es removible — sin el, no hay boton X
## Cuándo no usar
- Tags that are not interactive status labels should use `Tag`, not `Chip`.
## Criterio de uso
- Usa Chip para una selección, filtro o valor que el usuario pueda retirar; el botón de cierre debe estar dentro del mismo contexto semántico. - Usa `outlined` en reposo y `filled` cuando el estado seleccionado deba destacar; evita que la superficie sea la única señal. - Si el chip está bloqueado por permisos, explica la razón cerca del grupo y no simules una acción disponible.
## Gotchas
- El control de cierre necesita nombre accesible propio y debe conservar foco de teclado. - `type` es el prop visual del componente; no lo renombres a `variant` al componerlo con otros controles.
Uso
import { Chip } from '@/components/atoms/Chip';
<Chip type="filled" size="s" onClose={() => {}}>
Design
</Chip>Props
| Prop | Tipo | Default | Rango / opciones | What | How |
|---|---|---|---|---|---|
| type | select | outlined | `outlined`, `filled` | Surface style of the chip (outlined vs filled). | outlined for filters at rest; filled when selected. Default: outlined. |
| size | select | s | `xs`, `s`, `m`, `l`, `xl` | Padding and type scale of the chip. | s for filter bars; m for forms; xs only in dense tables. Default: s. |
| disabled | boolean | false | `true` / `false` | Prevents remove/select interaction. | true when the filter is locked by plan or permissions. Default: false. |
| error | boolean | false | `true` / `false` | Error emphasis on the chip surface. | true only with an associated validation message. Default: false. |
| focused | boolean | false | `true` / `false` | Forced focus ring for composed controls. | Prefer real :focus-visible; use focused only for composite widgets. Default: false. |
| animated | boolean | false | `true` / `false` | Motion on appear/dismiss. | true for filter UIs; false in static meta lists. Default: false. |
Gotchas
- a11y
onClose must have an accessible name (aria-label on the close control).
- react
variant prop is named type (not variant) — keep in sync with registry variantProp.
Anatomía CSS
<div class="chip"> <span class="chip__close"></span> <span class="chip__icon"></span> <span class="chip__label"></span> </div>
| Clase | Propósito |
|---|---|
chip | root |
chip--disabled | modifier |
chip--error | modifier |
chip--filled | modifier |
chip--focused | modifier |
chip--l | modifier |
chip--m | modifier |
chip--outlined | modifier |
chip--s | modifier |
chip--xl | modifier |
chip--xs | modifier |
chip__close | element |
chip__icon | element |
chip__label | element |
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 | fg | inherit | (literal) |
| all | border | none | (unparsed) |
| outlined | bg | #fafafa | background |
| outlined | border | #e5e5e5 | border |
| outlined | disabled-bg | #f84131 | destructive |
| filled | bg | #f5f5f5 | muted |
| filled | disabled-bg | #f84131 | destructive |
| all | disabled-fg | #525252 | muted.foreground |
| outlined | disabled-border | #f5f5f5 | muted |
| outlined | error-fg | #f84131 | destructive |
| outlined | error-border | #f84131 | destructive |
| filled | error-bg | #f84131 | destructive |
| filled | error-fg | #f84131 | destructive |
| all | bg | color-mix(in srgb, var(--foreground) 6%, transparent) | (literal) |
| all | error-bg | color-mix(in srgb, var(--destructive) 18%, transparent) | (literal) |
| all | disabled-bg | color-mix(in srgb, var(--foreground) 4%, transparent) | (literal) |
| all | hover-bg | color-mix(in srgb, var(--foreground) 12%, transparent) | (literal) |
Animaciones
| Propiedad | Duración | Easing |
|---|---|---|
background-color | var(--duration-150) | var(--easing-out) |
border-color | var(--duration-150) | var(--easing-out) |
scale | var(--duration-150) | var(--easing-in-out) |
background-color | 0.15s | cubic-bezier(0.19 |
1 | | |
0.22 | | |
1) | | |
border-color | 0.15s | cubic-bezier(0.19 |
1 | | |
0.22 | | |
1) | | |
scale | var(--duration-500) | 0.05s cubic-bezier(0.32 |
0.72 | | |
0 | | |
1) | | |
background-color | var(--duration-150) | var(--easing-out) |
scale | var(--duration-500) | cubic-bezier(0.32 |
0.72 | | |
0 | | |
1) | | |
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).
/* -------------------------------------------------------------------------
Chip
Interactive tag with optional leading icon and close button.
Same sizing discipline as Button: height + padding from tokens,
gap/radius/icon em-relative.
Types: outlined, filled
Sizes: xs, s, m, l, xl (same naming as Button)
States: enabled, hover, pressed, disabled, error
------------------------------------------------------------------------- */
.chip {
display: inline-flex;
align-items: center;
gap: 0.375em;
border-radius: 0.5em;
border: 1.5px solid transparent;
font-family: 'inter tight', -apple-system, blinkmacsystemfont, 'segoe ui', roboto, helvetica, arial, sans-serif, ui-sans-serif, system-ui, sans-serif;
font-size: 10.24px;
font-weight: 500;
line-height: 1;
color: #0a0a0a;
cursor: default;
user-select: none;
-webkit-tap-highlight-color: transparent;
white-space: nowrap;
transition:
background-color 150ms cubic-bezier(0.22, 1, 0.36, 1),
border-color 150ms cubic-bezier(0.22, 1, 0.36, 1),
scale 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
.chip[data-chip-animate]:active:not(.chip--disabled) {
scale: 0.955 0.925;
}
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
.chip[data-chip-animate]:hover:not(.chip--disabled) {
scale: 1.06 1.095;
transition:
background-color 0.15s cubic-bezier(0.19, 1, 0.22, 1),
border-color 0.15s cubic-bezier(0.19, 1, 0.22, 1),
scale 500ms 0.05s cubic-bezier(0.32, 0.72, 0, 1);
}
}
@media (prefers-reduced-motion: reduce) {
.chip {
transition-duration: 0ms;
}
.chip:active:not(.chip--disabled) {
scale: 1;
}
}
/* ---- Sizes (height + h-padding explicit, font-size uniform) ---- */
.chip--xs {
height: 1.25rem;
padding: 0 4px;
}
.chip--s {
height: 1.5rem;
padding: 0 8px;
}
.chip--m {
height: 2rem;
padding: 0 8px;
}
.chip--l {
height: 2.5rem;
padding: 0 12px;
}
.chip--xl {
height: 3rem;
padding: 0 16px;
}
/* ---- Types ---- */
.chip--outlined {
background-color: #fafafa;
border-color: #e5e5e5;
}
.chip--outlined:hover:not(.chip--disabled) {
background-color: #f5f5f5;
}
.chip--filled {
background-color: #f5f5f5;
}
.chip--filled:hover:not(.chip--disabled) {
background-color: #f5f5f5;
}
/* ---- Disabled ---- */
.chip--disabled {
color: #525252;
cursor: not-allowed;
pointer-events: none;
}
.chip--disabled.chip--outlined {
border-color: #f5f5f5;
background-color: #f5f5f5;
}
.chip--disabled.chip--filled {
background-color: #f5f5f5;
}
/* ---- Error ---- */
.chip--error.chip--outlined {
border-color: #f84131;
color: #f84131;
}
.chip--error.chip--outlined:hover:not(.chip--disabled) {
background-color: color-mix(in srgb, #f84131 6%, #fafafa);
}
.chip--error.chip--filled {
background-color: color-mix(in srgb, #f84131 8%, #fafafa);
color: #f84131;
}
.chip--error.chip--filled:hover:not(.chip--disabled) {
background-color: color-mix(in srgb, #f84131 12%, #fafafa);
}
/* ---- Focus ---- */
.chip:focus-visible,
.chip--focused {
box-shadow: 0 0 0 2px var(--focus-ring-color);
outline: none;
}
/* ---- Parts ---- */
.chip__icon {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 1em;
height: 1em;
}
.chip__icon svg {
width: 100%;
height: 100%;
}
.chip__label {
display: inline-flex;
align-items: center;
}
.chip__close {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 1.125em;
height: 1.125em;
padding: 0.125em;
border: none;
background-color: color-mix(in srgb, #0a0a0a 6%, transparent);
color: inherit;
cursor: pointer;
border-radius: 9999px;
transition:
background-color 150ms cubic-bezier(0.22, 1, 0.36, 1),
scale 500ms cubic-bezier(0.32, 0.72, 0, 1);
}
.chip__close svg {
width: 100%;
height: 100%;
}
.chip__close:active {
scale: 0.9;
}
.chip--error .chip__close {
background-color: color-mix(in srgb, #f84131 10%, transparent);
}
.chip--disabled .chip__close {
cursor: not-allowed;
pointer-events: none;
background-color: color-mix(in srgb, #0a0a0a 4%, transparent);
}
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
.chip__close:hover {
background-color: color-mix(in srgb, #0a0a0a 12%, transparent);
scale: 1.06 1.095;
}
.chip--error .chip__close:hover {
background-color: color-mix(in srgb, #f84131 18%, transparent);
}
}
Codigo fuente
import { type ReactNode } from 'react'; type ChipType = 'outlined' | 'filled'; type ChipSize = 'xs' | 's' | 'm' | 'l' | 'xl'; export type ChipProps = { type?: ChipType; size?: ChipSize; disabled?: boolean; error?: boolean; focused?: boolean; animated?: boolean; iconLeft?: ReactNode; onClose?: () => void; children: string; className?: string; }; function cn(...classes: (string | false | undefined | null)[]) { return classes.filter(Boolean).join(' '); } const CloseIcon = () => ( <svg width="100%" height="100%" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"> <path d="M4.5 4.5l7 7M11.5 4.5l-7 7" /> </svg> ); export function Chip({ type = 'outlined', size = 's', disabled = false, error = false, focused = false, animated = false, iconLeft, onClose, children, className, }: ChipProps) { const classes = cn( 'chip', `chip--${type}`, `chip--${size}`, disabled && 'chip--disabled', error && 'chip--error', focused && 'chip--focused', className, ); return ( <span className={classes} {...(animated ? { 'data-chip-animate': '' } : {})}> {iconLeft && <span className="chip__icon">{iconLeft}</span>} <span className="chip__label">{children}</span> {onClose && ( <button type="button" className="chip__close" onClick={onClose} disabled={disabled} aria-label={`Remove ${children}`} tabIndex={-1} > <CloseIcon /> </button> )} </span> ); }
Webflow
Pega en el Designer como application/json, luego convierte a Component (Atom / Chip) 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.chip[data-chip-animate]:active:not(.chip--disabled)— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)selectoron.chip--outlined:hover:not(.chip--disabled)— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)selectoron.chip--filled:hover:not(.chip--disabled)— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)selectoron.chip--disabled.chip--outlined— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)selectoron.chip--disabled.chip--filled— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)selectoron.chip--error.chip--outlined— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)selectoron.chip--error.chip--outlined:hover:not(.chip--disabled)— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)selectoron.chip--error.chip--filled— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)
Tras pegar: Create component → nombre Atom / Chip → Publish.