sdxc

Type to search, or start from one of these:

Form

The wrapper every multi-field layout starts from.

Shown in the sidebar and on invitations.

Teammates reach the workspace at this.acme.dev.

Invoices and renewal notices go here.

View code
let issues: ReadonlyArray<Issue> = [];
let created = false;

function submit(event: SubmitEvent) {
	event.preventDefault();
	let form = event.currentTarget as HTMLFormElement;
	issues = checkNewWorkspace(new FormData(form));
	created = issues.length === 0;
	void handle.update();
}

<Form
	method="post"
	issues={issues}
	// The native constraints below already block an empty submit with no script;
	// the schema is what catches the shapes an attribute cannot express.
	noValidate
	mix={[on<HTMLFormElement, "submit">("submit", submit)]}
>
	{created ? <Alert color="success">Workspace created.</Alert> : null}

	<TextField
		label="Workspace name"
		name="name"
		required
		description="Shown in the sidebar and on invitations."
	/>
	<TextField
		label="Address"
		name="slug"
		required
		description="Teammates reach the workspace at this.acme.dev."
	/>
	<TextField
		label="Billing owner"
		name="ownerEmail"
		type="email"
		required
		description="Invoices and renewal notices go here."
	/>

	<Label htmlFor="region">Data region</Label>
	<Select id="region" name="region">
		<Select.Option value="eu" selected>
			Europe — Frankfurt
		</Select.Option>
		<Select.Option value="us">United States — Iowa</Select.Option>
		<Select.Option value="ap">Asia Pacific — Sydney</Select.Option>
	</Select>

	<NumberField>
		<Label htmlFor="seats">Seats to reserve</Label>
		<NumberField.Group>
			<NumberField.DecrementButton aria-label="One fewer seat" />
			<NumberField.Input id="seats" name="seats" min={1} max={200} defaultValue={10} />
			<NumberField.IncrementButton aria-label="One more seat" />
		</NumberField.Group>
	</NumberField>

	<Checkbox
		name="terms"
		value="accepted"
		required
		aria-describedby="terms-error"
		aria-invalid={issueFor(issues, "terms") ? "true" : undefined}
	>
		I accept the data processing agreement
	</Checkbox>
	<FieldError id="terms-error" hidden={!issueFor(issues, "terms")}>
		{issueFor(issues, "terms")}
	</FieldError>

	<Button type="submit">Create workspace</Button>
</Form>

Installation

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

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

Usage

import { Form } from "@sdxc/ui";

Renders a native <form> laying children out in a single column, and provides Form.Context so descendant fields resolve their own issues by name and focus the first invalid field with no client JS. An issue with no path names no field to attach to, so it renders as its own leading Alert instead of being silently unreachable — a refusal decided before any field was reached still reaches the person filling the form in.

Props

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

PropTypeDescription
issues?ReadonlyArray<Issue>Validation issues from a parseSafe (or equivalent Standard Schema) result, provided to every descendant field through component context. Defaults to no issues, matching a first render.

Also accepts everything in TagProps<"form">.

Other components this module publishes.

  • readFormContext
  • resolveFieldIssue