Skip to content
kerfjsGitHub

SettingsLayout

The frame of a settings page: a list of sections beside a form that holds them.

SourcePackage @kerf-js/veneer

Preview

tsx
import { state } from '@kerf-js/core'import {  Button,  ConfirmDialog,  DangerZone,  Input,  Select,  SettingRow,  SettingsLayout,  SettingsSection,  Switch} from '@kerf-js/veneer'import type { State } from '@kerf-js/core'export function Project() {  const section = state('general')  const previews = state(true)  const failures = state(true)  const archive = state(false)  return (    <SettingsLayout      sections={SECTIONS}      current={section.value}      onNavigate={(id) => (section.value = id)}    >      <SettingsSection        id="general"        title="General"        description="How storefront appears across kerf."      >        <SettingRow layout="stack">          <Input            label="Project name"            defaultValue="storefront"          />        </SettingRow>        <SettingRow          title="Region"          description="Where its builds and logs live."        >          <Select            label="Region"            options={REGIONS}            defaultValue="eu-west"          />        </SettingRow>      </SettingsSection>      <ProjectDeploys previews={previews} />      <ProjectAlerts failures={failures} />      <DangerZone id="project-danger">        <SettingRow          title="Archive project"          description={[            'Stops deploys and makes it ',            'read-only.',          ].join('')}        >          <Button            emphasis="soft"            tone="critical"            size="sm"            onClick={() => (archive.value = true)}          >            Archive          </Button>        </SettingRow>      </DangerZone>      <ConfirmDialog        open={archive}        title="Archive storefront?"        description={[          'Deploys stop. You can restore it ',          'at any time.',        ].join('')}        action="Archive project"        onConfirm={() => wait(600)}      />    </SettingsLayout>  )}const wait = (ms: number) =>  new Promise<void>((done) => {    setTimeout(done, ms)  })const SECTIONS = [  { id: 'general', label: 'General' },  { id: 'deploys', label: 'Deploys' },  { id: 'alerts', label: 'Notifications' },  {    id: 'project-danger',    label: 'Danger zone',    tone: 'critical' as const,  },]const REGIONS = [  { value: 'eu-west', label: 'eu-west' },  { value: 'us-east', label: 'us-east' },  { value: 'ap-south', label: 'ap-south' },]function ProjectDeploys({  previews,}: {  previews: State<boolean>}) {  return (    <SettingsSection      id="deploys"      title="Deploys"    >      <SettingRow        title="Preview environments"        description="Every pull request gets its own URL."      >        {(ids: {          label: string          description: string        }) => (          <Switch            size="sm"            checked={previews.value}            aria-labelledby={ids.label}            aria-describedby={ids.description}            onChange={() =>              (previews.value = !previews.value)            }          />        )}      </SettingRow>    </SettingsSection>  )}function ProjectAlerts({  failures,}: {  failures: State<boolean>}) {  return (    <SettingsSection      id="alerts"      title="Notifications"    >      <SettingRow        title="Failed deploys"        description={[          "Mail the project's owner when a ",          'deploy fails.',        ].join('')}      >        {(ids: {          label: string          description: string        }) => (          <Switch            size="sm"            checked={failures.value}            aria-labelledby={ids.label}            aria-describedby={ids.description}            onChange={() =>              (failures.value = !failures.value)            }          />        )}      </SettingRow>    </SettingsSection>  )}

Installation

npm install @kerf-js/veneer

Usage

tsx
import { SettingsLayout } from '@kerf-js/veneer'

When to use

Use it for

  • A settings page with several sections
  • People need to jump to one part of a long form

Reach for something else

  • Tabs, when each part is its own page

Accessibility

  • The sections are a nav, and the current one has aria-current.

API Reference

PropTypeDefaultDescription
labelstring'Settings sections'Names the list of sections for screen readers.
sectionsSettingsNavItem[]—One entry per section: id matches a SettingsSection.
currentstring—The id of the section marked as current.
childrenunknown—What it contains.
EventTypeDefaultDescription
onNavigate((id: string) => void)—Called with the id when a section is pressed.

Parts

Every element carries a stable name, so you can style it from your own CSS: [data-scope="settings"] [data-part="layout"]

layoutnavcontent

RelatedSettings sectionSetting rowSave barDanger zone