Skip to content
ShineUI
Install

ConfigList

Settings that explain themselves.

<sui-config-list><sui-config-row>CompositionsStable

About

Settings view, the interactive relative of the description list. Each sui-config-row child pairs a term and description with a control and an optional preview; actions sit in a footer bar. Reach for it on a settings or connection page where every field needs a sentence of explanation beside it.

Built from

Usage

<sui-config-list heading="Export connection" subtitle="Where nightly ledger exports are delivered"> <sui-config-row term="Server" description="How we reach your server and what we call it."> <div slot="control" style="display:flex;flex-direction:column;gap:10px"> <sui-input-text label="URI" placeholder="ftp.example.com" size="md"></sui-input-text> <sui-input-text label="Friendly name" value="Falls City secure SFTP" size="md"></sui-input-text> </div> <div slot="preview" style="font-size:12.5px;color:var(--ink-muted);line-height:1.6"><b style="color:var(--ink)">Connection preview</b><br>Falls City secure SFTP connects over SSH and drops files in /exports/ledger.</div> </sui-config-row> <sui-config-row term="Credentials" description="How the server recognizes us."> <div slot="control" style="display:flex;flex-direction:column;gap:10px"> <sui-input-text label="Username" value="fallscity.exports" size="md"></sui-input-text> <sui-input-text label="Password" type="password" value="secretsecret" size="md"></sui-input-text> </div> </sui-config-row> <div slot="actions" style="display:flex;gap:9px"> <sui-button variant="outline" label="Cancel"></sui-button> <sui-button variant="brand" label="Save connection"></sui-button> </div> </sui-config-list> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); </script>
import { SuiConfigList } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { return ( <SuiConfigList heading="Export connection" subtitle="Where nightly ledger exports are delivered"> <sui-config-row term="Server" description="How we reach your server and what we call it."> <div slot="control" style="display:flex;flex-direction:column;gap:10px"> <sui-input-text label="URI" placeholder="ftp.example.com" size="md"></sui-input-text> <sui-input-text label="Friendly name" value="Falls City secure SFTP" size="md"></sui-input-text> </div> <div slot="preview" style="font-size:12.5px;color:var(--ink-muted);line-height:1.6"><b style="color:var(--ink)">Connection preview</b><br>Falls City secure SFTP connects over SSH and drops files in /exports/ledger.</div> </sui-config-row> <sui-config-row term="Credentials" description="How the server recognizes us."> <div slot="control" style="display:flex;flex-direction:column;gap:10px"> <sui-input-text label="Username" value="fallscity.exports" size="md"></sui-input-text> <sui-input-text label="Password" type="password" value="secretsecret" size="md"></sui-input-text> </div> </sui-config-row> <div slot="actions" style="display:flex;gap:9px"> <sui-button variant="outline" label="Cancel"></sui-button> <sui-button variant="brand" label="Save connection"></sui-button> </div> </SuiConfigList> ); }
<script setup> import { SuiConfigList } from "@shineui/vue"; import "@shineui/core/shineui.css"; </script> <template> <SuiConfigList heading="Export connection" subtitle="Where nightly ledger exports are delivered"><sui-config-row term="Server" description="How we reach your server and what we call it."> <div slot="control" style="display:flex;flex-direction:column;gap:10px"> <sui-input-text label="URI" placeholder="ftp.example.com" size="md"></sui-input-text> <sui-input-text label="Friendly name" value="Falls City secure SFTP" size="md"></sui-input-text> </div> <div slot="preview" style="font-size:12.5px;color:var(--ink-muted);line-height:1.6"><b style="color:var(--ink)">Connection preview</b><br>Falls City secure SFTP connects over SSH and drops files in /exports/ledger.</div> </sui-config-row> <sui-config-row term="Credentials" description="How the server recognizes us."> <div slot="control" style="display:flex;flex-direction:column;gap:10px"> <sui-input-text label="Username" value="fallscity.exports" size="md"></sui-input-text> <sui-input-text label="Password" type="password" value="secretsecret" size="md"></sui-input-text> </div> </sui-config-row> <div slot="actions" style="display:flex;gap:9px"> <sui-button variant="outline" label="Cancel"></sui-button> <sui-button variant="brand" label="Save connection"></sui-button> </div></SuiConfigList> </template>
import { Component } from "@angular/core"; import { SuiConfigList } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiConfigList], template: `<sui-config-list heading="Export connection" subtitle="Where nightly ledger exports are delivered"><sui-config-row term="Server" description="How we reach your server and what we call it."> <div slot="control" style="display:flex;flex-direction:column;gap:10px"> <sui-input-text label="URI" placeholder="ftp.example.com" size="md"></sui-input-text> <sui-input-text label="Friendly name" value="Falls City secure SFTP" size="md"></sui-input-text> </div> <div slot="preview" style="font-size:12.5px;color:var(--ink-muted);line-height:1.6"><b style="color:var(--ink)">Connection preview</b><br>Falls City secure SFTP connects over SSH and drops files in /exports/ledger.</div> </sui-config-row> <sui-config-row term="Credentials" description="How the server recognizes us."> <div slot="control" style="display:flex;flex-direction:column;gap:10px"> <sui-input-text label="Username" value="fallscity.exports" size="md"></sui-input-text> <sui-input-text label="Password" type="password" value="secretsecret" size="md"></sui-input-text> </div> </sui-config-row> <div slot="actions" style="display:flex;gap:9px"> <sui-button variant="outline" label="Cancel"></sui-button> <sui-button variant="brand" label="Save connection"></sui-button> </div></sui-config-list>`, }) export class Example { }

Basic

The preview column opens for the whole list because the first row supplies one. The list emits nothing itself: wire the controls inside a row and the buttons in the actions slot. The heading is an h2; heading-level moves it to h3 or h4 to sit under the page outline.

Connection preview
Falls City secure SFTP connects over SSH and drops files in /exports/ledger.
<sui-config-list heading="Export connection" subtitle="Where nightly ledger exports are delivered"> <sui-config-row term="Server" description="How we reach your server and what we call it."> <div slot="control" style="display:flex;flex-direction:column;gap:10px"> <sui-input-text label="URI" placeholder="ftp.example.com" size="md"></sui-input-text> <sui-input-text label="Friendly name" value="Falls City secure SFTP" size="md"></sui-input-text> </div> <div slot="preview" style="font-size:12.5px;color:var(--ink-muted);line-height:1.6"><b style="color:var(--ink)">Connection preview</b><br>Falls City secure SFTP connects over SSH and drops files in /exports/ledger.</div> </sui-config-row> <sui-config-row term="Credentials" description="How the server recognizes us."> <div slot="control" style="display:flex;flex-direction:column;gap:10px"> <sui-input-text label="Username" value="fallscity.exports" size="md"></sui-input-text> <sui-input-text label="Password" type="password" value="secretsecret" size="md"></sui-input-text> </div> </sui-config-row> <div slot="actions" style="display:flex;gap:9px"> <sui-button variant="outline" label="Cancel"></sui-button> <sui-button variant="brand" label="Save connection"></sui-button> </div> </sui-config-list>

Two columns

With no preview anywhere the row is a rail and a control. The row adds no ARIA of its own: the term and description read as plain text ahead of the control, so every field inside a row still carries its own label.

<sui-config-list heading="Ledger export" subtitle="What the nightly file contains"> <sui-config-row term="Format" description="The layout the accounting system expects."> <div slot="control"><sui-select id="cl-two-format" label="File format"></sui-select></div> </sui-config-row> <sui-config-row term="Retention" description="How long we keep exported files on the server."> <div slot="control"><sui-select id="cl-two-keep" label="Keep files for"></sui-select></div> </sui-config-row> <sui-config-row term="Notify" description="Who hears about a failed run."> <div slot="control"><sui-toggle-switch label="Email the controller" checked></sui-toggle-switch></div> </sui-config-row> </sui-config-list>
// setup(root): properties and events setup(root) { root.querySelector('#cl-two-format').options = ['CSV', 'Fixed width', 'QuickBooks IIF']; root.querySelector('#cl-two-keep').options = retention(); }

Controls of their own width at the row edge

control-width caps the control slot, so a workspace name does not sit in a field the width of the row, and control-align="end" puts the control at the trailing edge, the way a settings page lays its rows; the terms stay at the start. The list writes both onto every row; a row keeps its own when the list sets none.

<sui-config-list heading="Workspace" subtitle="What everyone sees" control-width="280px" control-align="end"> <sui-config-row term="Name" description="Shown in the rail and on every invite."> <div slot="control"><sui-input-text label="Workspace name" value="Larkspur"></sui-input-text></div> </sui-config-row> <sui-config-row term="Week starts on" description="The first column of the calendar."> <div slot="control"><sui-select id="cl-edge-week" label="First day"></sui-select></div> </sui-config-row> <sui-config-row term="Digest" description="A summary of the week, sent on Monday morning."> <div slot="control"><sui-toggle-switch label="Send the weekly digest" checked></sui-toggle-switch></div> </sui-config-row> </sui-config-list>
// setup(root): properties and events setup(root) { root.querySelector('#cl-edge-week').options = ['Monday', 'Sunday']; }

Narrow

Left alone, a row measures its own width and stacks term, description, control and a sunken preview panel below 560px, so a settings page fits a phone; this list sits in a 420px box. breakpoint moves the line; variant="mobile" or "desktop" pins one layout. The list writes both onto every row it owns.

Connection preview
Falls City secure SFTP connects over SSH and drops files in /exports/ledger.
<div style="max-width:420px"> <sui-config-list breakpoint="480" heading="Export connection" subtitle="Where nightly ledger exports are delivered"> <sui-config-row term="Server" description="How we reach your server and what we call it."> <div slot="control"><sui-input-text label="URI" value="ftp.fallscitybooks.com" size="md"></sui-input-text></div> <div slot="preview" style="font-size:12.5px;color:var(--ink-muted);line-height:1.6"><b style="color:var(--ink)">Connection preview</b><br>Falls City secure SFTP connects over SSH and drops files in /exports/ledger.</div> </sui-config-row> <sui-config-row term="Schedule" description="When the export runs."> <div slot="control"><sui-select id="cl-mobile-when" label="Run at"></sui-select></div> </sui-config-row> </sui-config-list> </div>
// setup(root): properties and events setup(root) { root.querySelector('#cl-mobile-when').options = ['9:00 PM', '11:00 PM', '2:00 AM']; }

A row on its own

A row works outside a list. Nothing writes preview-column for it then, so set it by hand; it still measures its own width, so leave variant unset unless one layout must hold.

Files older than the window are removed on the first of the month.
<div style="background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);padding:16px"> <sui-config-row id="cl-solo" term="Retention" description="How long we keep exported ledger files on the server." preview-column> <div slot="control"><sui-select id="cl-solo-keep" label="Keep files for"></sui-select></div> <div slot="preview" style="font-size:12.5px;color:var(--ink-muted);line-height:1.6">Files older than the window are removed on the first of the month.</div> </sui-config-row> </div>
// setup(root): properties and events setup(root) { root.querySelector('#cl-solo-keep').options = retention(); }

In a form

The card is layout, so the form is the element around it. Named controls inside the rows post as usual and the actions slot holds the submit and reset buttons.

Save to see the posted values.
<form id="cl-form"> <sui-config-list heading="Statement delivery" subtitle="How month-end statements reach Riverbend Dental Group"> <sui-config-row term="Delivery" description="Where the statement is sent and on what day."> <div slot="control" style="display:flex;flex-direction:column;gap:10px"> <sui-input-text label="Email" name="email" value="ap@riverbenddental.com" size="md"></sui-input-text> <sui-select id="cl-form-day" label="Send on" name="sendDay" value="Last day"></sui-select> </div> </sui-config-row> <sui-config-row term="Reminders" description="Chase an unpaid statement without anyone remembering to."> <div slot="control"><sui-toggle-switch label="Remind at 15 days past due" name="remind" value="on" checked></sui-toggle-switch></div> </sui-config-row> <div slot="actions" style="display:flex;gap:9px"> <sui-button type="reset" variant="outline" label="Reset"></sui-button> <sui-button type="submit" variant="brand" label="Save settings"></sui-button> </div> </sui-config-list> <output id="cl-form-out" style="display:block;margin-top:12px;font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Save to see the posted values.</output> </form>
// setup(root): properties and events setup(root) { const form = root.querySelector('#cl-form'); const out = root.querySelector('#cl-form-out'); root.querySelector('#cl-form-day').options = ['1st', '15th', 'Last day']; form.addEventListener('submit', (e) => { e.preventDefault(); out.textContent = [...new FormData(form)].map(([k, v]) => `${k}=${v}`).join(' '); }); }

A preview that arrives latersui-config-row

The row watches its own children, so a preview slotted in after mount opens the third column and removing it closes the column again. Set preview-column when the space should stay reserved either way.

<div style="background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);padding:16px"> <sui-config-row id="cr-late" term="Statement footer" description="Text printed under the totals on every statement."> <div slot="control" style="display:flex;flex-direction:column;gap:10px;align-items:flex-start"> <sui-textarea id="cr-late-text" label="Footer" rows="2" value="Thank you for your business."></sui-textarea> <sui-toggle-switch id="cr-late-show" label="Show a preview"></sui-toggle-switch> </div> </sui-config-row> </div>
// setup(root): properties and events setup(root) { const row = root.querySelector('#cr-late'); const text = root.querySelector('#cr-late-text'); const show = root.querySelector('#cr-late-show'); let preview = null; show.addEventListener('suiChange', (e) => { if (e.detail) { preview = document.createElement('div'); preview.slot = 'preview'; preview.style.cssText = PREVIEW; preview.textContent = text.value; row.appendChild(preview); } else if (preview) { preview.remove(); preview = null; } }); text.addEventListener('suiInput', () => { if (preview) preview.textContent = text.value; }); }

Data contract

Rows are { term, description, control, preview }. The preview column appears when any row supplies one. Actions render in a footer bar.

API

<sui-config-list>

Properties

PropertyAttributeTypeDefaultDescription
breakpointbreakpointnumbernumber | undefinedWidth in px below which the rows stack when no variant is set. Written onto every row the list owns; unset, each row keeps its own breakpoint, which defaults to 560.
controlAligncontrol-align"end" | "start" | undefinedWhere every row's control sits in its column, start or end. Written onto every row the list owns; unset, each row keeps its own controlAlign, which defaults to start. Reflected as control-align, and not called align: a browser reads an align attribute on any element as text-align, which would move the heading and every term with the controls.
controlWidthcontrol-widthstringstring | undefinedWidth of every row's control slot as a CSS length (360px, 24ch). Written onto every row the list owns; unset, each row keeps its own controlWidth, or fills its column.
headingheadingstringstring | undefinedCard title (the reference title prop; renamed for the native attribute).
headingLevelheading-level2 | 3 | 42Heading level of the title element, 2 to 4. The size does not change with it.
subtitlesubtitlestringstring | undefinedFaint line under the heading.
variantvariant"desktop" | "mobile" | undefineddesktop = two- or three-column grid rows; mobile = stacked rows with a sunken preview panel. Unset, every row measures its own width and stacks below breakpoint, so the list fits a phone without a switch. Written onto every row the list owns. Reflected.

Slots

SlotDescription
(default)sui-config-row children, one per setting. The preview column opens for every row when any row supplies a preview slot. The card keeps overflow visible, so select and picker panels inside rows escape it.
actionsSunken footer action bar (cancel and save buttons), right-aligned.

<sui-config-row>

Properties

PropertyAttributeTypeDefaultDescription
breakpointbreakpointnumber560Width of the row in px below which an unset variant stacks, measured by the row's own observer, so a row in a narrow column stacks whatever the screen is. Zero keeps the grid at every width. A sui-config-list with its own breakpoint writes it onto every row.
controlAligncontrol-align"end" | "start"'start'Where the control sits in its column: start (the default) or end, the row's trailing edge, where a settings page puts a control of its own width. Without controlWidth an end control shrinks to fit; with it, a field fills the column and a control of its own width, a switch or a segmented control, sits at the column's end, inside a bare wrapper div too. A sui-config-list with its own controlAlign writes it onto every row. Reflected as control-align, and not called align: a browser reads an align attribute on any element as text-align, which would move the term and the description along with the control.
controlWidthcontrol-widthstringstring | undefinedWidth of the control slot as a CSS length (360px, 24ch, 40%), so a short value does not sit in a field the width of the row. Unset, the control fills its column. A sui-config-list with its own controlWidth writes it onto every row.
descriptiondescriptionstringstring | undefinedOne or two sentences on what the setting does.
previewColumnpreview-columnbooleanfalseReserves the preview column even when this row has no preview. The parent list writes it when any sibling has one; set it by hand only when the row is used on its own.
termtermstring''Setting name ("Server").
variantvariant"desktop" | "mobile" | undefinedLayout: desktop is the grid, mobile the stack. Unset, the row measures its own width and stacks below breakpoint. The parent list writes its own value when it has one; set it by hand to pin one layout at every width. Reflected.

Slots

SlotDescription
controlThe field(s) for this setting.
previewOptional live preview or reference text beside the control.

Theming

Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.

See also