Form layout
tier 2 — classes.form-field stacks a label, a control and a hint; a .sedna-grid puts two
side by side; .form-section groups them and .form-actions closes the
form. The controls themselves each have their own page under Inputs.
Fields
Label, control, hint. The hint explains the consequence, not the control.
How often the queue refreshes. Shorter means more database load.
<div style="max-width:420px; display:flex; flex-direction:column; gap:16px">
<div class="form-field">
<label class="form-label" for="f-name">Display name</label>
<input class="form-input" id="f-name" type="text" value="Alex Fischer" />
</div>
<div class="form-field">
<label class="form-label" for="f-poll">Poll interval</label>
<input class="form-input" id="f-poll" type="text" value="30 seconds" />
<p class="form-hint">How often the queue refreshes. Shorter means more database load.</p>
</div>
<div class="form-field">
<label class="form-label form-label--required" for="f-owner">Owner</label>
<input class="form-input" id="f-owner" type="text" value="Fulfilment" required />
</div>
</div>
Two-up rows
.sedna-grid sedna-grid--sm sedna-grid--fit lays fields out side by side and collapses to one column when there
is not room for two — no breakpoint to pick.
<div class="sedna-grid sedna-grid--sm sedna-grid--fit" style="max-width:620px">
<div class="form-field">
<label class="form-label" for="f-from">Active from</label>
<input class="form-input" id="f-from" type="time" value="18:00" />
</div>
<div class="form-field">
<label class="form-label" for="f-to">Active to</label>
<input class="form-input" id="f-to" type="time" value="06:00" />
</div>
</div>
A field across the whole row
.sedna-span-full on a child of that grid — the long field
among the short ones: a description, a Markdown editor, a checkbox line, a hint that belongs
to the row rather than to one field. It works in
any grid, down to a single-column phone, so there is no breakpoint and no
style="grid-column:1 / -1" in the markup.
Shown on the queue's own page and in the picker.
<div class="sedna-grid sedna-grid--sm sedna-grid--fit" style="max-width:620px">
<div class="form-field">
<label class="form-label" for="fr-name">Queue name</label>
<input class="form-input" id="fr-name" type="text" value="Returns — north" />
</div>
<div class="form-field">
<label class="form-label" for="fr-owner">Owner</label>
<input class="form-input" id="fr-owner" type="text" value="Alex Fischer" />
</div>
<div class="form-field sedna-span-full">
<label class="form-label" for="fr-what">What this queue is for</label>
<textarea class="form-input" id="fr-what" rows="3">Returns that need a manual credit note before the refund is sent.</textarea>
<p class="form-hint">Shown on the queue's own page and in the picker.</p>
</div>
<label class="form-check sedna-span-full">
<input type="checkbox" checked /> Hold new items until someone claims them
</label>
</div>
Sections and actions
.form-section is a <fieldset> with the browser's border and
inset legend removed. Use the real element — the legend is announced with every control
in the group, which a heading above a <div> is not — and put the
fields in .form-section-body, because a <legend> is not a flex
item and lands somewhere different in every engine if the fieldset is the flex container.
<form style="max-width:520px">
<fieldset class="form-section">
<legend>Escalation</legend>
<p class="form-section-hint">Who is paged when nobody has acknowledged in time.</p>
<div class="form-section-body">
<div class="form-field">
<label class="form-label form-label--required" for="f-team">Team</label>
<select class="form-input form-select" id="f-team" required>
<option selected>Fulfilment</option>
<option>Network</option>
</select>
</div>
<div class="form-field">
<label class="form-label" for="f-after">Escalate after</label>
<div class="input-group">
<input class="form-input" id="f-after" type="number" value="15" />
<span class="input-affix">minutes</span>
</div>
</div>
</div>
</fieldset>
<div class="form-actions form-actions--split">
<button class="btn btn-danger" type="button"><i class="ri-delete-bin-line"></i> Delete rule</button>
<span style="display:flex; gap:8px">
<button class="btn" type="button">Cancel</button>
<button class="btn btn-primary" type="submit">Save</button>
</span>
</div>
</form>
Where the actions sit
.form-actions wraps instead of overflowing, so three buttons on a phone stack
rather than pushing the commit button off-screen. --split puts a destructive
action where it cannot be hit by aiming for Save; --start aligns
the row to the leading edge, for a form embedded in a page rather than closing a dialog.
Default — the row ends where the form ends.
--split — the destructive one is put out of reach of the safe one.
--start — for a narrow form whose fields all begin at the same edge.
<div class="sedna-col" style="max-width:520px; gap:20px">
<div class="card">
<div class="card-body">
<p class="form-hint" style="margin:0 0 12px">Default — the row ends where the form ends.</p>
<div class="form-actions">
<button class="btn" type="button">Cancel</button>
<button class="btn btn-primary" type="button">Save</button>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<p class="form-hint" style="margin:0 0 12px"><code>--split</code> — the destructive one is put out of reach of the safe one.</p>
<div class="form-actions form-actions--split">
<button class="btn btn-danger" type="button"><i class="ri-delete-bin-line"></i> Delete rule</button>
<button class="btn btn-primary" type="button">Save</button>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<p class="form-hint" style="margin:0 0 12px"><code>--start</code> — for a narrow form whose fields all begin at the same edge.</p>
<div class="form-actions form-actions--start">
<button class="btn btn-primary" type="button">Add member</button>
<button class="btn" type="button">Cancel</button>
</div>
</div>
</div>
</div>
A group that is not a fieldset
.form-section-title on a plain heading, for a region rather than a set of related
controls. Same rule, without claiming a grouping that is not there — a
<legend> tells a screen reader these answers belong together.
Danger zone
A heading and .form-section-title, because this groups content rather than controls — nothing here shares an answer.
<div class="sedna-col" style="max-width:520px; gap:24px">
<fieldset class="form-section">
<legend>Escalation</legend>
<p class="form-section-hint">A <code>fieldset</code> and a <code>legend</code>, because these three controls are one decision.</p>
<div class="form-section-body">
<label class="form-check"><input type="radio" name="st-esc" checked /> Page the on-call rota</label>
<label class="form-check"><input type="radio" name="st-esc" /> Page the team channel</label>
<label class="form-check"><input type="radio" name="st-esc" /> Do not page</label>
</div>
</fieldset>
<section class="form-section">
<h3 class="form-section-title">Danger zone</h3>
<p class="form-section-hint">A heading and <code>.form-section-title</code>, because this groups content rather than controls — nothing here shares an answer.</p>
<div class="form-section-body">
<div class="form-actions form-actions--start">
<button class="btn btn-danger" type="button">Transfer ownership</button>
<button class="btn btn-danger" type="button">Delete project</button>
</div>
</div>
</section>
</div>
A whole form
Every piece on this page at once, as a starting point to paste and cut down.
<form style="max-width:640px">
<fieldset class="form-section">
<legend>Reservation</legend>
<p class="form-section-hint">What is being held, and for whom.</p>
<div class="form-section-body">
<div class="sedna-grid sedna-grid--sm sedna-grid--fit">
<div class="form-field">
<label class="form-label form-label--required" for="wf-order">Order</label>
<input class="form-input" id="wf-order" type="text" value="ORD-4182" required />
</div>
<div class="form-field">
<label class="form-label form-label--required" for="wf-cust">Customer</label>
<input class="form-input" id="wf-cust" type="text" value="Priya Nair" required />
</div>
</div>
<div class="form-field">
<label class="form-label" for="wf-note">Note on the hold</label>
<textarea class="form-input" id="wf-note" rows="3"
placeholder="What the warehouse should know before releasing it"></textarea>
</div>
</div>
</fieldset>
<fieldset class="form-section">
<legend>When it expires</legend>
<div class="form-section-body">
<div class="form-field">
<label class="form-label" for="wf-hold">Hold for</label>
<div class="input-group sedna-w sedna-w--sm">
<input class="form-input" id="wf-hold" type="number" value="24" />
<span class="input-affix">hours</span>
</div>
<p class="form-hint">After this the slot returns to the pool without asking.</p>
</div>
<label class="switch">
<input type="checkbox" checked />
<span class="choice-text">
<span>Warn me before it expires</span>
<span class="choice-hint">One notification, an hour ahead.</span>
</span>
</label>
</div>
</fieldset>
<div class="form-actions form-actions--split">
<button class="btn btn-danger" type="button"><i class="ri-close-circle-line"></i> Release now</button>
<span style="display:flex; gap:8px">
<button class="btn" type="button">Cancel</button>
<button class="btn btn-primary" type="submit">Hold order</button>
</span>
</div>
</form>
A settings card
Fields in a card, actions in a footer row.
Notifications are suppressed during this window. The queue still updates.
<div class="card" style="max-width:520px">
<div class="card-head"><strong>Notifications</strong></div>
<div class="card-body" style="display:flex; flex-direction:column; gap:14px">
<label class="form-check"><input type="checkbox" checked /> Notify me about new pending items</label>
<div class="form-field">
<label class="form-label" for="f-quiet">Quiet hours</label>
<input class="form-input" id="f-quiet" type="text" value="22:00 – 07:00" />
<p class="form-hint">Notifications are suppressed during this window. The queue still updates.</p>
</div>
</div>
<div class="modal-footer">
<button class="btn">Reset</button>
<button class="btn btn-primary">Save</button>
</div>
</div>