Item
A single selectable row with optional leading/trailing slots. Lists, menus, and pickers.
Variants: defaultoutlinemuted (default: default)
Sizes: defaultsmxs (default: default)
Editorial
<!-- F12c editorial — non-derivable only. Review: Karen. -->
## Ejemplos
Selectable list row:
```tsx import { Item, ItemMedia, ItemContent, ItemTitle, ItemDescription, ItemActions, } from '@/components/atoms/Item';
<Item variant="outline" size="default" href="/docs"> <ItemMedia variant="icon">{/* icon */}</ItemMedia> <ItemContent> <ItemTitle>Docs</ItemTitle> <ItemDescription>Guides and API</ItemDescription> </ItemContent> <ItemActions>→</ItemActions> </Item> ```
## Accesibilidad
- With `href`, root renders `<a>`; otherwise `<div>` — add keyboard handlers/role if clickable without href. - Prefer real links for navigation so open-in-new-tab and middle-click work.
## Cuándo no usar
- Form options in a closed list → `Select` / `Radio`. - Dense data grids → `Table`.
## Criterio de uso
- Usa Item para filas con una jerarquía clara de media, título, descripción y acción; mantén el contenido secundario breve. - Si la fila navega, usa `href` para conservar semántica, foco, historial y abrir en nueva pestaña. - Usa `variant` y `size` para expresar densidad y agrupación, no para convertir cada fila en una tarjeta visual independiente.
## Gotchas
- Un Item clickeable sin `href` necesita teclado, role y estados equivalentes; es preferible un enlace real cuando el destino es navegación. - Los slots deben conservar orden lógico para tecnologías asistivas: título antes de metadatos y acción con nombre claro.
Uso
import {
Item, ItemMedia, ItemContent, ItemTitle, ItemDescription, ItemActions,
} from '@/components/atoms/Item';
<Item variant="outline" size="default" href="/docs">
<ItemMedia variant="icon">{/* icon */}</ItemMedia>
<ItemContent>
<ItemTitle>Docs</ItemTitle>
<ItemDescription>Guides and API</ItemDescription>
</ItemContent>
<ItemActions>→</ItemActions>
</Item>Props
| Prop | Tipo | Default | Rango / opciones | What | How |
|---|---|---|---|---|---|
| variant | select | default | `default`, `outline`, `muted` | Surface emphasis of the row. | default lists; outline for bordered menus; muted for secondary rows. Default: default. |
| size | select | default | `default`, `sm`, `xs` | Vertical density of the item. | default in main lists; sm/xs in sidebars and dense pickers. Default: default. |
Gotchas
- react
With href, root renders <a>; otherwise <div>. Compose ItemMedia/Content/Title/Description/Actions; group with ItemGroup.
- a11y
If clickable without href, add keyboard handlers and role; prefer href for navigation.
Anatomía CSS
<div class="item-separator"> </div>
| Clase | Propósito |
|---|---|
item-separator | 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 | bg | #e5e5e5 | border |
| outline | border | none | (unparsed) |
| all | fg | #525252 | muted.foreground |
| all | hover-bg | #f5f5f5 | accent |
| muted | bg | #f5f5f5 | muted |
| muted | hover-bg | #f5f5f5 | accent |
| all | border | none | (unparsed) |
Animaciones
| Propiedad | Duración | Easing |
|---|---|---|
background-color | var(--duration-100) | var(--easing-in-out) |
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).
/* -------------------------------------------------------------------------
Item
Versatile content row: media + title + description + actions.
For lists, settings, notifications, etc.
Variants: default, outline, muted
Sizes: default, sm, xs
Parts: .item, .item__media, .item__content, .item__title,
.item__description, .item__actions, .item-group,
.item-separator
------------------------------------------------------------------------- */
/* ---- Item ---- */
.item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
font-family: 'inter tight', -apple-system, blinkmacsystemfont, 'segoe ui', roboto, helvetica, arial, sans-serif, ui-sans-serif, system-ui, sans-serif;
text-decoration: none;
color: inherit;
transition: background-color 100ms cubic-bezier(0.4, 0, 0.2, 1);
}
.item:hover {
background-color: #f5f5f5;
}
/* ---- Variants ---- */
.item--outline {
border: 1px solid #e5e5e5;
border-radius: 12px;
}
.item--muted {
background-color: #f5f5f5;
border-radius: 12px;
}
.item--muted:hover {
background-color: #f5f5f5;
}
/* ---- Sizes ---- */
.item--sm {
gap: 8px;
padding: 8px 12px;
}
.item--xs {
gap: 8px;
padding: 4px 8px;
}
/* ---- Media ---- */
.item__media {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
color: #525252;
}
.item__media--icon {
width: 40px;
height: 40px;
border-radius: 12px;
background-color: #f5f5f5;
padding: 8px;
}
.item--sm .item__media--icon {
width: 32px;
height: 32px;
padding: 8px;
}
.item__media--icon svg {
width: 100%;
height: 100%;
}
.item__media--image {
width: 48px;
height: 48px;
border-radius: 8px;
overflow: hidden;
}
.item__media--image img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
/* ---- Content ---- */
.item__content {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
/* ---- Title ---- */
.item__title {
font-size: 12.8px;
font-weight: 500;
line-height: 1.45;
color: #0a0a0a;
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* ---- Description ---- */
.item__description {
font-size: 10.24px;
line-height: 1.4;
color: #525252;
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* ---- Actions ---- */
.item__actions {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
/* ---- Group ---- */
.item-group {
display: flex;
flex-direction: column;
}
.item-group--outline {
border: 1px solid #e5e5e5;
border-radius: 12px;
overflow: hidden;
}
.item-group--outline > .item {
border: none;
border-radius: 0;
}
/* ---- Separator ---- */
.item-separator {
height: 1px;
background-color: #e5e5e5;
}
/* ---- Reduced motion ---- */
@media (prefers-reduced-motion: reduce) {
.item {
transition-duration: 0ms;
}
}
Codigo fuente
import { type ReactNode, type HTMLAttributes } from 'react'; function cn(...classes: (string | false | undefined | null)[]) { return classes.filter(Boolean).join(' '); } /* ---- Item ---- */ export type ItemProps = { variant?: 'default' | 'outline' | 'muted'; size?: 'default' | 'sm' | 'xs'; href?: string; children: ReactNode; className?: string; } & HTMLAttributes<HTMLElement>; export function Item({ variant = 'default', size = 'default', href, children, className, ...props }: ItemProps) { const classes = cn( 'item', variant !== 'default' && `item--${variant}`, size !== 'default' && `item--${size}`, className, ); if (href) { return <a href={href} className={classes} {...(props as any)}>{children}</a>; } return <div className={classes} {...props}>{children}</div>; } /* ---- Media ---- */ export type ItemMediaProps = { variant?: 'default' | 'icon' | 'image'; children: ReactNode; className?: string; }; export function ItemMedia({ variant = 'default', children, className }: ItemMediaProps) { return ( <div className={cn('item__media', variant !== 'default' && `item__media--${variant}`, className)}> {children} </div> ); } /* ---- Content ---- */ export function ItemContent({ children, className }: { children: ReactNode; className?: string }) { return <div className={cn('item__content', className)}>{children}</div>; } /* ---- Title ---- */ export function ItemTitle({ children, className }: { children: ReactNode; className?: string }) { return <p className={cn('item__title', className)}>{children}</p>; } /* ---- Description ---- */ export function ItemDescription({ children, className }: { children: ReactNode; className?: string }) { return <p className={cn('item__description', className)}>{children}</p>; } /* ---- Actions ---- */ export function ItemActions({ children, className }: { children: ReactNode; className?: string }) { return <div className={cn('item__actions', className)}>{children}</div>; } /* ---- Group ---- */ export type ItemGroupProps = { variant?: 'default' | 'outline'; children: ReactNode; className?: string; }; export function ItemGroup({ variant = 'default', children, className }: ItemGroupProps) { return ( <div className={cn('item-group', variant === 'outline' && 'item-group--outline', className)}> {children} </div> ); } /* ---- Separator ---- */ export function ItemSeparator({ className }: { className?: string }) { return <div className={cn('item-separator', className)} />; }
Webflow
Pega en el Designer como application/json, luego convierte a Component (Atom / Item) 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.item--sm .item__media--icon— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)selectoron.item__media--icon svg— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)selectoron.item__media--image img— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)selectoron.item-group--outline > .item— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)@mediaon(prefers-reduced-motion: reduce)— not a Designer breakpoint — moved to head Custom Code block
Tras pegar: Create component → nombre Atom / Item → Publish.