Item
A row with media, a title, a description and actions.
In Dense data
See it live
npm run storybookWhen to use it
In Dense data: Short lists such as users or settings, each with a few actions. Not for records analysts compare; those go in a table.
- Use it when: Lists of things such as people, files or settings, each with a few actions.
- Not when: Records people compare cell by cell (a table).
Instead:
Variants
What each variant is for in any design system, then what Dense data decided and why. Each sample is the live story with that variant set.
defaultvariantUsed in Dense dataWhat it is for
No border and no fill: the row sits on the surface around it.
In Dense data
Users and settings in a panel or menu, with no border, quiet beside the data.
outlinevariantNot used in Dense dataWhat it is for
A border around each row.
In Dense data
Borders on every row would compete with the data; hairlines between sections do that work.
mutedvariantUsed in Dense dataWhat it is for
A soft fill behind the row.
In Dense data
The current user or setting, with a soft fill so people keep their place.
defaultsizeUsed in Dense dataWhat it is for
Comfortable padding.
In Dense data
Settings and user lists outside the table, at the standard row.
smsizeUsed in Dense dataWhat it is for
Tighter padding.
In Dense data
Long lists in a side panel, at the denser size. It still fits a mouse.
Why it works this way
Item looks the way it does because of 6 answers about this product. Each card is one answer, in three steps: the answer, what it decided for Item, and what would move if it changed.
content.q11The answer
Most of the screen is numbers and records.
2So Item
- ✓uses default: Users and settings in a panel or menu, with no border, quiet beside the data.
- –does not use outline: Borders on every row would compete with the data; hairlines between sections do that work.
Badge: neutral Badge: info Badge: success Badge: warning Badge: danger Sidebar: default Sidebar: outline Empty: default Toggle: default Toggle: outline3If it changed
and 44 answers that follow from it: A lot, Scan and compare, People keep their place, and 41 more.
elevation.definition1The answer
Thin lines guide the eye along rows.
2So Item
- –does not use outline: Borders on every row would compete with the data; hairlines between sections do that work.
3If it changed
and 3 answers that follow from it: Only what floats, One, Menus and dialogs.
tasks.q11The answer
People scan rows and compare values.
2So Item
- ✓uses muted: The current user or setting, with a soft fill so people keep their place.
Field: vertical3If it changed
and 20 answers that follow from it: People keep their place, Acting on the wrong record, Core and data, and 17 more.
human.q11The answer
Losing your place in a long table costs time and errors.
2So Item
- ✓uses muted: The current user or setting, with a soft fill so people keep their place.
3If it changed
and 8 answers that follow from it: Lines, Feedback only, One, and 5 more.
spacing.count1The answer
A second, denser size serves tables and toolbars.
2So Item
- ✓uses default: Settings and user lists outside the table, at the standard row.
- ✓uses sm: Long lists in a side panel, at the denser size. It still fits a mouse.
Field: horizontal Sidebar: sm3If it changed
No other answer follows from it.
accessibility.values1The answer
Mouse and keyboard; compact targets are enough.
2So Item
- ✓uses sm: Long lists in a side panel, at the denser size. It still fits a mouse.
Button: default Button: compact Toggle: sm3If it changed
No other answer follows from it.
Words
- Lead with the object, then the state: "Alert 2041 contained."
- Use numbers, not words for numbers.
- One verb per button. No "please", no exclamation marks.
- Use: Investigate, Filter, Assign, Escalated, Resolved. Avoid: Oops, Awesome, Please, Simply.
The labels components use, from the voice tokens:
content.action.commit: Savecontent.action.cancel: Cancelcontent.action.destroy: Deletecontent.action.retry: Retrycontent.action.empty-action: Change filters
See Voice and tone for the whole voice.
Use it in code
npx open-ds add item
The component reads only component keys and semantic tokens, so it follows the brand and mode on <html>.
Tokens it reads
Item reads 32 tokens of its own, each an alias of a semantic token. Each row is one part: its own token, the semantic it aliases, and the value it ends at. Change a semantic and every component that reads it follows; override one of Item's tokens to change Item alone.
Dense data decides what changes when you switch theme. Here a switch changes colors only, so the marked rows differ between light and dark; everything else holds steady.
| Part | Its token and the semantic it aliases | Value |
|---|---|---|
| Item | ||
| Corner | component.item.radiusradius.controlvar(--item-radius) | radius.2:0.125rem |
| Text size | component.item.font-sizetype.body.font-sizevar(--item-font-size) | font.size.14:0.875rem |
| Speed | component.item.durationmotion.duration.feedbackvar(--item-duration) | duration.fast:120ms |
| Border focus Changes with theme on focus | component.item.border-focuscolor.border.focusvar(--item-border-focus) | Lightviolet.light.600:#4B3FD9Darkviolet.dark.600:#A9A3F6 |
| Background hover Changes with theme on hover | component.item.background-hovercolor.surface.sunkenvar(--item-background-hover) | Lightslate.light.100:#EEF1F5Darkslate.dark.100:#262D38 |
| Focus on focus | component.item.focusborder-width.focusvar(--item-focus) | border-width.2:2px |
| Focus ring focus Changes with theme on focus | component.item.focus-ring-focuscolor.border.focusvar(--item-focus-ring-focus) | Lightviolet.light.600:#4B3FD9Darkviolet.dark.600:#A9A3F6 |
| Focus ring offset focus Changes with theme on focus | component.item.focus-ring-offset-focuscolor.surface.canvasvar(--item-focus-ring-offset-focus) | Lightslate.light.50:#F7F8FADarkslate.dark.0:#0D1117 |
| Gap | component.item.gapspace.gap.defaultvar(--item-gap) | space.8:0.5rem |
| Outline | ||
| Border Changes with theme | component.item.outline.bordercolor.border.defaultvar(--item-outline-border) | Lightslate.light.200:#DDE2EADarkslate.dark.100:#262D38 |
| Muted | ||
| Fill Changes with theme | component.item.muted.backgroundcolor.surface.sunkenvar(--item-muted-background) | Lightslate.light.100:#EEF1F5Darkslate.dark.100:#262D38 |
| Default | ||
| Gap | component.item.default.gapspace.gap.groupvar(--item-default-gap) | space.16:1rem |
| Padding | component.item.default.paddingspace.inset.containervar(--item-default-padding) | space.16:1rem |
| Sm | ||
| Gap | component.item.sm.gapcomponent-spacing-map.x3var(--item-sm-gap) | space.12:0.75rem |
| Padding, left and right | component.item.sm.padding-xspace.inset.containervar(--item-sm-padding-x) | space.16:1rem |
| Padding, top and bottom | component.item.sm.padding-ycomponent-spacing-map.x3var(--item-sm-padding-y) | space.12:0.75rem |
| Icon | ||
| Size | component.item.icon.sizesize.control.defaultvar(--item-icon-size) | size.32:2rem |
| Corner | component.item.icon.radiusradius.badgevar(--item-icon-radius) | radius.0:0rem |
| Fill Changes with theme | component.item.icon.backgroundcolor.surface.sunkenvar(--item-icon-background) | Lightslate.light.100:#EEF1F5Darkslate.dark.100:#262D38 |
| Icon size | component.item.icon.icon-sizesize.iconvar(--item-icon-icon-size) | size.16:1rem |
| Image | ||
| Corner | component.item.image.radiusradius.badgevar(--item-image-radius) | radius.0:0rem |
| Content | ||
| Gap | component.item.content.gapspace.gap.tightvar(--item-content-gap) | space.4:0.25rem |
| Title | ||
| Gap | component.item.title.gapspace.gap.defaultvar(--item-title-gap) | space.8:0.5rem |
| Text size | component.item.title.font-sizetype.body.font-sizevar(--item-title-font-size) | font.size.14:0.875rem |
| Weight | component.item.title.font-weighttype.label.font-weightvar(--item-title-font-weight) | font.weight.500:500 |
| Description | ||
| Text size | component.item.description.font-sizetype.body.font-sizevar(--item-description-font-size) | font.size.14:0.875rem |
| Weight | component.item.description.font-weighttype.body.font-weightvar(--item-description-font-weight) | font.weight.400:400 |
| Text Changes with theme | component.item.description.labelcolor.text.mutedvar(--item-description-label) | Lightslate.light.600:#535D6DDarkslate.dark.400:#969FAD |
| Label 2 Changes with theme | component.item.description.label-2color.action.primaryvar(--item-description-label-2) | Lightviolet.light.600:#4B3FD9Darkviolet.dark.600:#A9A3F6 |
| Actions | ||
| Gap | component.item.actions.gapspace.gap.defaultvar(--item-actions-gap) | space.8:0.5rem |
| Header | ||
| Gap | component.item.header.gapspace.gap.defaultvar(--item-header-gap) | space.8:0.5rem |
| Footer | ||
| Gap | component.item.footer.gapspace.gap.defaultvar(--item-footer-gap) | space.8:0.5rem |
| From the system | ||
| Edge Changes with theme | color.border.default straight from the system, through --border | Lightslate.light.200 · #DDE2EADarkslate.dark.100 · #262D38 |