Row
A row is one square high. It arranges pieces horizontally with one of three alignments, and with href or interactive the whole row becomes one press target.
Design examples
Section titled “Design examples”The row opens the recording; its end-slot control remains a separate target.
<section class="specimen">
<ds-row class="line" interactive><span class="mark"><ds-icon type="music" size="large" aria-hidden="true"></ds-icon></span><ds-text literal class="grow">Morning tide</ds-text><ds-text literal class="mono">04:32</ds-text><ds-button slot="end" secondary label="Download recording"><span class="mark"><ds-icon type="download" size="large" aria-hidden="true"></ds-icon></span></ds-button></ds-row>
</section>
<style>
.specimen { width: 100%; min-width: 0; --cell: calc(20px * var(--sf, 1)); }
.specimen .line { width: 100%; }
.specimen .grow { flex: 1; min-width: 0; }
.specimen .muted { color: var(--ds-text-color-muted); }
.specimen .mono { --ds-typeface-regular: var(--ds-typeface-mono); font-variant-numeric: tabular-nums; }
.specimen .value { width: calc(4 * var(--cell)); text-align: right; }
.specimen .mark { display: inline-flex; align-items: flex-end; width: var(--cell); height: var(--ds-control-height); flex: 0 0 var(--cell); }
.specimen .columns { display: flex; flex-wrap: wrap; gap: var(--cell); }
.specimen .columns > * { min-width: 0; }
.specimen fieldset { border: 0; padding: 0; margin: 0; }
.specimen legend { padding: 0; }
.specimen label { display: flex; align-items: flex-end; gap: calc(var(--cell) / 2); min-height: var(--cell); }
.specimen ds-panel { height: calc(10 * var(--cell)); }
</style> Short and long labels share a stable value edge.
<section class="specimen">
<ds-row class="line" ><ds-text literal class="grow">Paper size</ds-text><ds-text literal tone="muted">A4</ds-text></ds-row><ds-row class="line" ><ds-text literal class="grow">Binding</ds-text><ds-text literal tone="muted">Thread sewn</ds-text></ds-row><ds-row class="line" ><ds-text literal class="grow">Copies in this edition</ds-text><ds-text literal tone="muted">150</ds-text></ds-row>
</section>
<style>
.specimen { width: 100%; min-width: 0; --cell: calc(20px * var(--sf, 1)); }
.specimen .line { width: 100%; }
.specimen .grow { flex: 1; min-width: 0; }
.specimen .muted { color: var(--ds-text-color-muted); }
.specimen .mono { --ds-typeface-regular: var(--ds-typeface-mono); font-variant-numeric: tabular-nums; }
.specimen .value { width: calc(4 * var(--cell)); text-align: right; }
.specimen .mark { display: inline-flex; align-items: flex-end; width: var(--cell); height: var(--ds-control-height); flex: 0 0 var(--cell); }
.specimen .columns { display: flex; flex-wrap: wrap; gap: var(--cell); }
.specimen .columns > * { min-width: 0; }
.specimen fieldset { border: 0; padding: 0; margin: 0; }
.specimen legend { padding: 0; }
.specimen label { display: flex; align-items: flex-end; gap: calc(var(--cell) / 2); min-height: var(--cell); }
.specimen ds-panel { height: calc(10 * var(--cell)); }
</style> Try it
Section titled “Try it”Fill places content at opposite ends of the row.
<ds-row fill>
<ds-text>Language</ds-text>
<ds-text>English</ds-text>
</ds-row> An icon and its label share one row.
<ds-row centered>
<ds-icon type="check"></ds-icon>
<ds-text>Completed</ds-text>
</ds-row> Attributes
Section titled “Attributes”| Attribute | Type | Default | What it does |
|---|---|---|---|
fill | boolean | false | Pushes content to opposite ends. |
centered | boolean | false | Centres the group with small gaps. |
end | boolean | false | Places the group at the end. |
auto | boolean | false | Takes the content’s height instead of one square. |
stacked | boolean | false | Stacks the content, each line one square: a tile or a card. |
href | string | Makes the whole row one link to this address. | |
interactive | boolean | false | Makes the whole row one button. |
disabled | boolean | false | Keeps the row from being pressed and dims it. A disabled link has no href and stays a link with aria-disabled="true". |
current | string | Marks the destination being shown: aria-current="page", or the value given (step, location, date, time). | |
nav | boolean | false | Draws a navigation row, with the sidebar’s two-pixel corners. |
label | string | The press target’s accessible name, when the row’s words do not say what it does. | |
target, rel | string | Passed to the link. |
Use one alignment at a time. Slot children can be ordinary HTML or components. The standard row is twelve squares wide and one square high for these alignments.
A row a person presses
Section titled “A row a person presses”Press a row, or its own action at the end. The event appears below.
<ds-list style="width:calc(12 * var(--ds-unit))">
<ds-row href="#atlas"><ds-text literal>Atlas</ds-text><ds-text tone="muted" literal style="margin-inline-start:auto">12 items</ds-text></ds-row>
<ds-row interactive><ds-text literal>Harbor</ds-text><ds-text tone="muted" literal style="margin-inline-start:auto">4 items</ds-text>
<ds-button slot="end" label="Add to Harbor"><ds-icon type="plus"></ds-icon></ds-button></ds-row>
<ds-row interactive disabled><ds-text literal>Meadow</ds-text></ds-row>
</ds-list>
<ds-row interactive stacked style="width:calc(4 * var(--ds-unit))"><ds-text size="small" tone="muted" literal>Cash</ds-text><ds-text literal>¥1,200</ds-text></ds-row> document.addEventListener('ds-row-press', event => {
event.preventDefault();
document.querySelector('#event-output').textContent = 'Pressed: ' + event.target.querySelector('ds-text').textContent;
}); With href the row renders one link around its content; with interactive, one button. The pointer over the row fills it with --ds-row-fill-hover, a press with --ds-row-fill-press, an open row (data-open, its details shown beside the list) with --ds-row-fill-current, all as one layer a square high, two pixels around what the row holds (from two above the lower sixteen to two below the row’s foot, two past the content at each side) with 6px corners that follow the 4px icon tiles, drawn over the row so its words and icons stay where they stand on the grid; and keyboard focus draws the focus ring two pixels inside the row, so rows stacked edge to edge never overlap each other’s ring. The content keeps the row’s layout: alignment, gap and stacked apply inside the press target as they would outside it. The words stand 4px from the row’s edges (--ds-row-padding).
A control of its own, such as a quick action or a menu, goes in the end slot. It stands outside the press target and above it, so pressing it never presses the row; pressing anywhere else does. Do not put links or buttons in the default slot: they would be nested inside the row’s own link or button.
ds-row-press fires when the row is pressed, with { href }. Cancel it with preventDefault() to route a link yourself; a link pressed with a modifier key or the middle button is left to the browser, so new tabs and downloads keep working. row.focus() focuses the press target.
A tile or a card is a row with stacked and a width in whole squares: each child takes its own line of one square.
<ds-row interactive stacked style="width: calc(4 * var(--ds-unit))"> <ds-text size="small" tone="muted" literal>Cash</ds-text> <ds-text literal>¥1,200</ds-text></ds-row>The parts of a record
Section titled “The parts of a record”A row that stands for a record (a payment, an event) names its parts with classes, so the page writes no layout of its own: a 16px picture first (an Avatar tile or a large Icon), which takes the first sixteen of its square while the row’s 4px gap puts the words on the next; the words in ds-row__name, which hug and give way first; a Tag after them where there is one; and the figure or date in ds-row__value, at the row’s end in tabular figures. A name wider than its room fades over its last square, never an ellipsis: the element measures it and marks it data-cut="end" while it is cut, so a name that fits stays crisp. A ds-text literal name fades the same way. A page without Lit marks the native .ds-row__name with startCutFades() from 0-face/cut-fade.ts. The List shows the rows under their headings.
<ds-row href="/coffee"> <ds-avatar shape="square" icon="coffee" color="var(--ds-yellow)"></ds-avatar> <ds-text class="ds-row__name" literal>Coffee</ds-text> <ds-tag>Cash</ds-tag> <ds-text class="ds-row__value" literal>5.05 kr</ds-text></ds-row>Navigation rows
Section titled “Navigation rows”The page being shown is the current row. Choose another.
<nav aria-label="Account" style="width:calc(12 * var(--ds-unit))">
<ds-row href="#profile" nav current><ds-text literal>Profile</ds-text></ds-row>
<ds-row href="#billing" nav><ds-text literal>Billing</ds-text></ds-row>
<ds-row href="#members" nav><ds-text literal>Members</ds-text></ds-row>
</nav> const rows = document.querySelectorAll('nav ds-row');
document.addEventListener('ds-row-press', event => {
event.preventDefault();
rows.forEach(row => { row.current = row === event.target ? 'page' : null; });
}); A list of destinations is a <nav> of rows with href and nav; the page being shown takes current. The current row is drawn as the Sidebar’s: filled with --ds-row-fill-current and in the medium weight (--ds-row-current-weight), on two-pixel corners. Never mark the current page with the accent or a colour of its own.
Rows with open details
Section titled “Rows with open details”Set open on the row whose details are showing. This fills it with the current-row colour without changing its text weight. The component exposes aria-expanded on its press target; it does not toggle itself. Keep the property tied to the displayed record, update it when stepping between records, and set it to false when the details close. Leave it unset on rows that do not open details.
<ds-row interactive open><span>Selected record</span></ds-row><button class="ds-row" data-open aria-expanded="true">Selected record</button>Native rows use data-open on .ds-row, including a row with a separate .ds-row__target. Remove it (or set it to false) when closed, and update aria-expanded on the button that opens the details. The application owns record identity and opening/closing; hover never clears the open state. A native table uses data-open on its body <tr> instead.