Button
An action or navigation control, with primary, secondary and text treatments.
Design examples
Section titled “Design examples”Primary, secondary and quiet actions share one baseline.
<section class="specimen">
<ds-row class="line" ><ds-button primary><span class="mark"><ds-icon type="check" size="large" aria-hidden="true"></ds-icon></span>Publish</ds-button><ds-button secondary>Save draft</ds-button><ds-button >Preview</ds-button><ds-button disabled>Archive</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> Icon-only controls retain accessible names and the same control height.
<section class="specimen">
<ds-row class="line" ><ds-button secondary label="Previous photograph"><span class="mark"><ds-icon type="left" size="large" aria-hidden="true"></ds-icon></span></ds-button><ds-text literal class="mono">12 of 48</ds-text><ds-button secondary label="Next photograph"><span class="mark"><ds-icon type="right" size="large" aria-hidden="true"></ds-icon></span></ds-button><ds-button secondary><span class="mark"><ds-icon type="download" size="large" aria-hidden="true"></ds-icon></span>Download</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> Try it
Section titled “Try it”Click a button. The event appears below.
<ds-button primary>Save changes</ds-button>
<ds-button secondary>Cancel</ds-button>
<ds-button>Text button</ds-button>
<ds-button primary disabled>Disabled</ds-button> Icons follow the button’s text colour.
<ds-button primary>
<ds-icon type="check"></ds-icon> Save
</ds-button> Choose a treatment
Section titled “Choose a treatment”Use primary for the main action, secondary for a supporting action, and no attribute for a text button. These are boolean attributes: primary="false" still enables primary. Remove the attribute to disable it.
Attributes
Section titled “Attributes”| Attribute | Type | Default | What it does |
|---|---|---|---|
primary | boolean | false | Accent background. |
secondary | boolean | false | Quiet background. |
disabled | boolean | false | Disables the inner button and its action; a link loses its destination. |
fixed | boolean | false | Four-square width instead of hugging the label. |
wide | boolean | false | Eight-square width. |
fit | boolean | false | Explicit alias for the default label-hugging width. |
text | string | Empty | Translation key; otherwise use slot content. |
label | string | Empty | The control’s accessible name, as given, when the face alone does not say what the button does (a value or a number). Pass words already in the page’s language. |
href | string | Empty | Makes the face a real link (<a>) to this address. |
target | string | Empty | The link’s target. |
rel | string | Empty | The link’s rel; a new tab gets noopener noreferrer without it. |
blank | boolean | false | Opens href in a new tab (target="_blank"). |
variant="primary" is not supported. Use <ds-button primary>.
Listen for an action
Section titled “Listen for an action”const button = document.querySelector('ds-button');button.addEventListener('click', () => { console.log('Save requested');});The native click crosses the shadow boundary once. A disabled button does not trigger it.
Navigation and forms
Section titled “Navigation and forms”With href="/icons/" the face is a real link drawn as the button: the browser navigates, so middle-click,
⌘- or Ctrl-click, Copy link and Enter work as on any link, and nothing navigates from a click handler. A
disabled link has no href. React uses ButtonLink; see
Links. For forms, use control="slotted". A native light-DOM button supplies submission,
reset, focus, validation and disabled-fieldset behavior; DS1 owns its visual
appearance. Keep button attributes on the native control and visual options on
ds-button.
<ds-button control="slotted" primary> <button type="submit" name="action" value="save">Save</button></ds-button>Set disabled on the host; it forwards to the native control. Native form
attributes (form, formaction, formnovalidate, formmethod, name, value)
keep their browser behavior. The light-DOM control remains the actual submitter.
The shared face is protected from document button resets; configure it through
public --ds-button-* tokens and put layout on the host.
React products select button in their snapshot and import the shared binding:
import { Button } from './design/react/button';<Button type="submit" primary disabled={saving}>Save</Button>Every face, including React and native slotted controls, is one control high and stands at the foot of its line, so its words take the lower sixteen of the square; a menu entry is a row high with its words on the same sixteen. Buttons hug their label by default, including React and native slotted controls. fit remains an explicit alias. Optional --ds-button-min-width and --ds-button-align tokens control a minimum width and label alignment without reaching into the shadow root.
Slotted buttons are one row high. Use fixed for four squares, wide for eight squares, icon for one square, or --ds-button-width for a layout-sized control. Menu items retain their full-row width.
The adapter’s default type is button; set submit or reset deliberately.
Refs and event targets are the real native button. className and style apply
to the host for layout; ARIA, IDs, native attributes and handlers reach the
native control. Registration happens in the browser before hydration, as for
shared inputs. The adapter does not import browser globals during SSR.
Use danger for destructive actions: the words are in the danger colour (--ds-danger-color) on every face, the default one included, as .ds-button--danger draws them on a native button. It can be combined with secondary.
The native control exposes ::part(control) for a library’s authored box model. Use the host for margins. The library editor measures the real control and writes opt-in padding, border, corner and gap rules; without these rules, standard button geometry stays unchanged.
Split mode
Section titled “Split mode”A compact group for views, frame choices or repeated actions such as back, today and next. The chosen segment carries the accent. Only the outside corners take the current shape; interior joins stay square.
Choose a view or step through the date.
<ds-button split aria-label="View" options="day:Day,week:Week,month:Month" value="week"></ds-button>
<ds-button split actions aria-label="Date" options="back:@left:Back,today:Today,next:@right:Next"></ds-button> <ds-button split aria-label="View" options="day:Day,week:Week,month:Month" value="week"></ds-button><ds-button split actions aria-label="Date" options="back:@left:Back,today:Today,next:@right:Next"></ds-button>Choices and actions
Section titled “Choices and actions”options="day:Day,week:Week" supplies value/label pairs. A bare word is its
own label and lowercase value. back:@left:Back draws the shared left icon
with Back as its accessible label. Labels follow the shared language runtime;
set literal for already translated or user-supplied words.
For arbitrary labels, including commas and colons, set the segments property
to an array of { value, label, icon?, disabled? } objects. An empty array
renders no segments. disabled disables the entire group, and a disabled
fieldset disables it through the browser’s form association.
Set value for a choice. Choosing a different segment emits a bubbling,
composed, cancelable change with detail.value. The value changes after the
event unless a listener calls preventDefault(). Choosing the current value
does nothing. With actions, every activation emits press with detail.value
and none of the segments is selected.
Each segment is a native button: Tab focuses it, Enter and Space activate it.
The group never submits a form or adds a form value. Give the host an
aria-label; focus() focuses the selected enabled segment, or the first
enabled segment in action mode.
Split is a Button mode, selected with split; it is grouped under Button in the catalogue and library editor. In a library, choose Single or Split in Button mode. Each mode keeps its own saved box-model rules; split segments retain square interior joins. In React, use the same Button entry:
import { Button } from './design/react/button';<Button split label="Frame" value={frame} onPress={setFrame} segments={[{ value: "browser", label: "Browser" }, { value: "phone", label: "Phone" }]} /><Button split label="History" onPress={handleHistory} segments={[{ value: "undo", label: "Undo", icon: "undo" }, { value: "redo", label: "Redo", icon: "redo", disabled: !canRedo }]} />Supplying value keeps the choice controlled; omitting it selects repeated actions. compact keeps a single-character label square and gives longer numeric labels the compressed face. Outside corners follow the shared shape; interior joins stay square. The native segments retain Tab, Enter, Space and disabled-fieldset behavior. This mode groups choices or actions; it does not create a dropdown menu.
There is no separate split element: select button in Sync 1 and write <ds-button split> or <Button split>.