Forms
The one surface where the reader has to do something rather than read
something. Every element here wraps a real control — an <input>, a
<select>, a <textarea>, a <fieldset> — so the keyboard, the tap
target, the platform's validation and how it all reads out arrive with it.
Anything drawn instead looks right in a screenshot and cannot be typed in.
sds-field#
A text field, a text area and a select, in one element. It has two shapes and
the difference is caption.
<sds-field caption="Your email" field-id="email" name="email"
type="email" hint="Used for the reply and for nothing else."
required></sds-field><sds-field caption="Your email" field-id="email" name="email"
type="email" hint="Used for the reply and for nothing else."
required></sds-field>
With a caption it renders the row a form owes a control: label above,
hint under, error under both. The label is a real <label> pointing
at the control's own id, so pressing the words reaches the field and an
error summary can send a reader straight to it.
<sds-field caption="Your email" field-id="email" name="email"
type="email" hint="Used for the reply and for nothing else."
required></sds-field><sds-field caption="Your email" field-id="email" name="email"
type="email" hint="Used for the reply and for nothing else."
required></sds-field>
With a caption it renders the row a form owes a control: label above,
hint under, error under both. The label is a real <label> pointing
at the control's own id, so pressing the words reaches the field and an
error summary can send a reader straight to it.
<sds-field icon="actions-search" value="Filter tools"
label="Filter tools" min-width="260"></sds-field><sds-field icon="actions-search" value="Filter tools"
label="Filter tools" min-width="260"></sds-field>
Without one it is the bare control — right in a header, a toolbar, a
filter row, where the surface around it says what it is for. It still
owes a label, because nothing visible names it.
<sds-field icon="actions-search" value="Filter tools"
label="Filter tools" min-width="260"></sds-field><sds-field icon="actions-search" value="Filter tools"
label="Filter tools" min-width="260"></sds-field>
Without one it is the bare control — right in a header, a toolbar, a
filter row, where the surface around it says what it is for. It still
owes a label, because nothing visible names it.
<sds-field caption="Your email" field-id="email" name="email"
type="email" hint="Used for the reply and for nothing else."
required></sds-field><sds-field caption="Your email" field-id="email" name="email"
type="email" hint="Used for the reply and for nothing else."
required></sds-field>
With a caption it renders the row a form owes a control: label above,
hint under, error under both. The label is a real <label> pointing
at the control's own id, so pressing the words reaches the field and an
error summary can send a reader straight to it.
<sds-field caption="Your email" field-id="email" name="email"
type="email" hint="Used for the reply and for nothing else."
required></sds-field><sds-field caption="Your email" field-id="email" name="email"
type="email" hint="Used for the reply and for nothing else."
required></sds-field>
With a caption it renders the row a form owes a control: label above,
hint under, error under both. The label is a real <label> pointing
at the control's own id, so pressing the words reaches the field and an
error summary can send a reader straight to it.
<sds-field icon="actions-search" value="Filter tools"
label="Filter tools" min-width="260"></sds-field><sds-field icon="actions-search" value="Filter tools"
label="Filter tools" min-width="260"></sds-field>
Without one it is the bare control — right in a header, a toolbar, a
filter row, where the surface around it says what it is for. It still
owes a label, because nothing visible names it.
<sds-field icon="actions-search" value="Filter tools"
label="Filter tools" min-width="260"></sds-field><sds-field icon="actions-search" value="Filter tools"
label="Filter tools" min-width="260"></sds-field>
Without one it is the bare control — right in a header, a toolbar, a
filter row, where the surface around it says what it is for. It still
owes a label, because nothing visible names it.
The visible label, which turns this into a field in a form.
-
caption# -
- type
- string
The visible label, which turns this into a field in a form.
What the control is called for anything that cannot see what it sits beside. A field with no visible label of its own owes one here.
-
label# -
- type
- string
What the control is called for anything that cannot see what it sits beside. A field with no visible label of its own owes one here.
What is in the field: its value when filled, its placeholder when not.
-
value# -
- type
- string
What is in the field: its value when
filled, its placeholder when not.
The browser's business — email, tel, url decide which keyboard a
phone offers and what the platform validates before anything of ours runs.
-
type# -
- type
- string
- default
- "text"
The browser's business —
email,tel,urldecide which keyboard a phone offers and what the platform validates before anything of ours runs.
The control heights a button has, so a field and the button beside it stand
on one line. sm is for a field inside another surface — a toolbar, a
table head, a filter row — and never for making a form fit; lg is the
field a screen is for, beside the large button that answers it. In a form
the label follows the control, so the words never outweigh the box.
-
size# -
- type
- "md" | "sm" | "lg"
- default
- "md"
The control heights a button has, so a field and the button beside it stand on one line.
smis for a field inside another surface — a toolbar, a table head, a filter row — and never for making a form fit;lgis the field a screen is for, beside the large button that answers it. In a form the label follows the control, so the words never outweigh the box.
-
name# -
- type
- string
The control's id, so the label points at it and an error summary can.
-
field-id# -
- type
- string
The control's id, so the label points at it and an error summary can.
Lines. Anything above one renders a <textarea>, not a taller input: the
difference is what the browser does with a newline.
-
rows# -
- type
- number
- default
- 0
Lines. Anything above one renders a
<textarea>, not a taller input: the difference is what the browser does with a newline.
A select rather than a text field: the same sunken box, closed by a chevron.
-
select# -
- type
- boolean
- default
- false
A select rather than a text field: the same sunken box, closed by a chevron.
What a select offers. A text field ignores it.
-
options# -
- type
- "string[]"
What a select offers. A text field ignores it.
What the answer has to be, under the control. Never inside it.
-
hint# -
- type
- string
What the answer has to be, under the control. Never inside it.
What is wrong with what is in it. Sets the invalid state with it, so the colour and the sentence cannot disagree: a field that says what is wrong without being marked wrong is two halves of one state, and halves drift.
-
error# -
- type
- string
What is wrong with what is in it. Sets the invalid state with it, so the colour and the sentence cannot disagree: a field that says what is wrong without being marked wrong is two halves of one state, and halves drift.
Said in words beside the label, never as an asterisk that needs a legend somewhere else on the page.
-
required# -
- type
- boolean
- default
- false
Said in words beside the label, never as an asterisk that needs a legend somewhere else on the page.
The width it asks for — and what it gets is that or the room there is. It
was a min-width once, which wins over every other width rule in CSS: a
field asking for 260px in a header with 240px left pushed the page sideways,
and nothing in the row looked wrong.
-
min-width# -
- type
- number
- default
- 220
The width it asks for — and what it gets is that or the room there is. It was a
min-widthonce, which wins over every other width rule in CSS: a field asking for 260px in a header with 240px left pushed the page sideways, and nothing in the row looked wrong.
-
icon# -
- type
- icon id
-
focused# -
- type
- boolean
-
invalid# -
- type
- boolean
The three states exist for a specimen, which is a still picture and can
hold neither focus nor invalidity. Set none of them and the states are the
browser's; typing sets filled itself.
-
filled# -
- type
- boolean
The three states exist for a specimen, which is a still picture and can hold neither focus nor invalidity. Set none of them and the states are the browser's; typing sets
filleditself.
A placeholder is not a label. It leaves exactly when it is needed — the moment someone starts typing — and it is invisible to anything reading the page as a document.
A placeholder is not a label. It leaves exactly when it is needed — the moment someone starts typing — and it is invisible to anything reading the page as a document.
sds-field-error#
The message under an invalid field, with its own glyph, because colour alone is not a message.
<sds-field-error message="An address needs an @ in it."></sds-field-error><sds-field-error message="An address needs an @ in it."></sds-field-error>
-
messagerequired # -
- type
- string
An element of its own rather than a slot on the field: an error is often
written by whatever validated the value, which is not always what drew the box.
sds-field renders one for you when it is given error.
Never a tooltip. An error the pointer has to find is an error the keyboard never surfaces at all.
Never a tooltip. An error the pointer has to find is an error the keyboard never surfaces at all.
sds-checkbox#
One thing that is either so or not — the platform's own control in this system's colours, not a box with a glyph in it.
<sds-checkbox name="digest" value="weekly" label="Send me the weekly digest"
hint="One message, on Fridays. Unsubscribe from any of them."></sds-checkbox><sds-checkbox name="digest" value="weekly" label="Send me the weekly digest"
hint="One message, on Fridays. Unsubscribe from any of them."></sds-checkbox>
A real <label> wraps both, so the words are part of the target: a 16px box
is hard to hit and the sentence beside it is not.
-
labelrequired # -
- type
- string
What ticking it commits to, where the label cannot say it in a line.
-
hint# -
- type
- string
What ticking it commits to, where the label cannot say it in a line.
-
checked# -
- type
- boolean
- default
- false
Neither on nor off: the box stands for a set only some of which is ticked. Ticking it resolves to on, the way the platform resolves it.
-
indeterminate# -
- type
- boolean
- default
- false
Neither on nor off: the box stands for a set only some of which is ticked. Ticking it resolves to on, the way the platform resolves it.
-
name# -
- type
- string
-
value# -
- type
- string
-
required# -
- type
- boolean
-
disabled# -
- type
- boolean
sds-radio#
One answer out of a few, all of them visible. The set is the component and a single button is not: what makes one a choice is the set it belongs to, the name they share and that exactly one holds.
<sds-radio legend="How should we reply?" name="reply" value="email"
.choices="${[{ label: 'By email', value: 'email' },
{ label: 'In the issue', value: 'issue',
hint: 'Public, and the thread stays with the code.' }]}"
></sds-radio><sds-radio legend="How should we reply?" name="reply" value="email"
.choices="${[{ label: 'By email', value: 'email' },
{ label: 'In the issue', value: 'issue',
hint: 'Public, and the thread stays with the code.' }]}"
></sds-radio>
What is being asked. Rendered as the <legend> of a real <fieldset>.
-
legendrequired # -
- type
- string
What is being asked. Rendered as the
<legend>of a real<fieldset>.
-
namerequired # -
- type
- string
-
choicesrequired # -
- type
- "{ label, value?, hint? }[]"
The chosen value, or the label where a choice has none.
-
value# -
- type
- string
The chosen value, or the label where a choice has none.
-
hint# -
- type
- string
-
required# -
- type
- boolean
Where the answers are many, or the reader already knows the one they want, that is a select. Above roughly five the set stops being scannable.
Where the answers are many, or the reader already knows the one they want, that is a select. Above roughly five the set stops being scannable.
sds-form-errors#
What stopped the form, at the top of it.
<sds-form-errors announce
.errors="${[{ message: 'The message is empty', for: 'message' }]}"
></sds-form-errors><sds-form-errors announce
.errors="${[{ message: 'The message is empty', for: 'message' }]}"
></sds-form-errors>
Marking the boxes is enough for a reader who sees the whole form at once and nothing at all for one who does not: a phone is sent back to a page that looks unchanged, a screen reader is told nothing happened. So the summary is where the reader lands — first, focusable, announced, and every line a link to the field it names.
for is the id of the field, so pressing an entry moves the focus to the
control rather than to a heading above it.
-
errorsrequired # -
- type
- "{ message, for? }[]"
foris the id of the field, so pressing an entry moves the focus to the control rather than to a heading above it.
What the form calls itself, so the heading names the thing that failed rather than saying "there were errors".
-
heading# -
- type
- string
What the form calls itself, so the heading names the thing that failed rather than saying "there were errors".
That this is the result of a submit the reader just made, so send them to it. Left off, the summary is drawn and takes nothing — which is what a page returned by a server with its errors already in it needs, because taking the focus there pulls a reader out of wherever they were.
-
announce# -
- type
- boolean
- default
- false
That this is the result of a submit the reader just made, so send them to it. Left off, the summary is drawn and takes nothing — which is what a page returned by a server with its errors already in it needs, because taking the focus there pulls a reader out of wherever they were.
It renders sds-note rather than drawing the error block again: what a
failure looks like is decided once.
Sent, and reset#
Two behaviours worth knowing before a form is assembled out of these.
A button in a form is not a submit unless it says so. sds-button
defaults to type="button" for that reason — see Controls. Give the
one button that sends the form type="submit", and Enter in a text field
then submits too, which is the behaviour only that button should carry.
A reset puts back what the markup said. sds-checkbox and sds-radio
keep the state the page was drawn with and restore it when the form around them
is reset — not the last thing that was clicked, which is what mirroring the
live state into the attribute would have restored.
Forms is the same subject as a set of rules: why the label is above, why the summary is the part that is always skipped, and what a form owes a reader who cannot see all of it at once.
Forms is the same subject as a set of rules: why the label is above, why the summary is the part that is always skipped, and what a form owes a reader who cannot see all of it at once.