SearchCtrlK

  • ↑↓navigate
  • ↵open
  • escclose

Can’t find it? Contact us

SettingsSection

One group of settings: a title and description beside or above a bordered card of 1px-ruled rows.

Monitor defaults

Applied to every monitor you create from now on.

How often each URL is requested.

A check slower than this counts as down.

Notifications

Each change saves as soon as you flip it.

Email me when a monitor goes down
Weekly uptime report

Sent on Monday at 9:00 in your time zone.

Members

People who can see and edit this team's monitors.

EmailRole
ada@acme.devOwner
grace@acme.devAdmin
linus@acme.devMember

Billing

Your plan and how you pay for it.

Subscription & payments

Change your plan, update the card on file and download invoices.

Cancel subscription

Your monitors keep running until the end of the billing period, then stop. Invoices stay available in the billing portal.

View code
tsx
<SettingsSection
	title="Monitor defaults"
	description="Applied to every monitor you create from now on."
	save="explicit"
	form={{ action: "/settings/monitors" }}
	labels={{ save: "Save defaults" }}
>
	<SettingsSection.Row label="Check interval" description="How often each URL is requested." htmlFor="interval">
		<Select id="interval" name="interval">
			<Select.Option value="30">Every 30 seconds</Select.Option>
			<Select.Option value="60" selected>Every minute</Select.Option>
			<Select.Option value="300">Every 5 minutes</Select.Option>
		</Select>
	</SettingsSection.Row>
	<SettingsSection.Row label="Timeout" description="A check slower than this counts as down." htmlFor="timeout">
		<Input id="timeout" name="timeoutMs" type="number" defaultValue="10000" />
	</SettingsSection.Row>
	<SettingsSection.Row label="Alert email" htmlFor="alert-email" layout="stacked">
		<Input id="alert-email" name="alertEmail" type="email" defaultValue="oncall@acme.dev" />
	</SettingsSection.Row>
</SettingsSection>

<SettingsSection
	title="Notifications"
	description="Each change saves as soon as you flip it."
	save="instant"
	form={{ action: "/settings/notifications" }}
	labels={{ save: "Save notifications" }}
>
	<SettingsSection.Row layout="inline-switch" label="Email me when a monitor goes down" labelId="down">
		<Switch name="emailOnDown" aria-labelledby="down" checked />
	</SettingsSection.Row>
	<SettingsSection.Row
		layout="inline-switch"
		label="Weekly uptime report"
		description="Sent on Monday at 9:00 in your time zone."
		labelId="weekly"
		descriptionId="weekly-help"
	>
		<Switch name="weeklyReport" aria-labelledby="weekly" aria-describedby="weekly-help" />
	</SettingsSection.Row>
</SettingsSection>

<SettingsSection
	title="Members"
	description="People who can see and edit this team's monitors."
	save="none"
	actions={<Button size="sm" variant="outline">Invite member</Button>}
>
	<Table.Container>
		<Table aria-label="Members">
			<Table.Header>
				<Table.Row>
					<Table.Column>Email</Table.Column>
					<Table.Column>Role</Table.Column>
				</Table.Row>
			</Table.Header>
			<Table.Body>
				{MEMBERS.map((member) => (
					<Table.Row key={member.email}>
						<Table.Cell>{member.email}</Table.Cell>
						<Table.Cell>{member.role}</Table.Cell>
					</Table.Row>
				))}
			</Table.Body>
		</Table>
	</Table.Container>
</SettingsSection>

<SettingsSection title="Billing" description="Your plan and how you pay for it." save="none">
	<SettingsSection.Row
		layout="action"
		label="Subscription & payments"
		description="Change your plan, update the card on file and download invoices."
	>
		<LinkButton href="/billing/portal" size="sm" variant="outline">Open billing portal</LinkButton>
	</SettingsSection.Row>
	<SettingsSection.Row
		layout="action"
		actionPlacement="below"
		label="Cancel subscription"
		description="Your monitors keep running until the end of the billing period, then stop. Invoices stay available in the billing portal."
	>
		<Button size="sm" variant="outline" color="danger">Cancel subscription</Button>
	</SettingsSection.Row>
</SettingsSection>

Demo apps

Whole pages built only from @sdxc/ui, each for an invented product with its own palette and type. They open in a new tab.

Every demo, landings and docs sites alike, is listed in the demo gallery.

Installation

An ordinary dependency: install it, import the theme once, and import the component where it is used.

Terminal
npm add @sdxc/ui remix
typescript
import "@sdxc/ui/theme.css";

Usage

typescript
import { SettingsSection } from "@sdxc/ui/app";

Renders the section. The card is a <form> when form is set; an explicit section ends in a footer holding its own Save, which ⌘S presses while focus is in the section, an instant one submits whenever a control changes, and a "none" one is a plain card.

Composition

The parts the component publishes, each a static property of the host.

  • SettingsSection
  • SettingsSection.RowRenders one setting as a label and help beside or above its control.

Examples

tsx
<SettingsSection title="Notifications" save="instant" form={{ action: "/settings/notifications" }} labels={{ save: "Save" }}>
	<SettingsSection.Row layout="inline-switch" label="Email me when a monitor goes down" labelId="down-label">
		<Switch name="emailOnDown" aria-labelledby="down-label" />
	</SettingsSection.Row>
</SettingsSection>
tsx
<SettingsSection title="Members" save="none" actions={<Button commandfor="invite" command="show-modal">Invite member</Button>}>
	<Table.Container>…</Table.Container>
</SettingsSection>

Props

Read from the component's own types, so every prop, every default and every allowed value is listed.

SettingsSection.Row

PropTypeRequiredDescription
labelRemixNodeYes
descriptionRemixNodeNoHelp under the label: what the setting changes, in a sentence.
htmlForstringNoThe control's id, which turns the label into a <label for>.
labelIdstringNoThe label's id, for a control named through aria-labelledby, such as a Switch.
descriptionIdstringNoThe description's id, for a control's aria-describedby.
layout"split" | "stacked" | "inline-switch" | "action"No"split" puts the label at the start and the control at the end, stacking under 32rem; "stacked" puts the control under the label; "inline-switch" keeps a small control at the end at every width; "action" gives the text the row's width and puts a button that acts at once, such as "Open billing portal", where actionPlacement says, stacking under the text below 32rem.
actionPlacementActionPlacementNoWhere an "action" row's control sits on a wide card.

Also accepts everything in TagProps<"div">.

Types

NameValuesWhat it decides
Save"explicit" | "instant" | "none"How the section's changes persist.
RowLayout"split" | "stacked" | "inline-switch" | "action"Where a row's label sits against its control.
@sdxc/ui

Can’t find it? Contact us