pub struct Field<'a> {Show 15 fields
pub kind: FieldKind,
pub name: &'a str,
pub label: &'a str,
pub hint: Option<&'a str>,
pub error: Option<&'a str>,
pub placeholder: Option<&'a str>,
pub options: &'a [Choice<'a>],
pub accept: &'a [Accepted<'a>],
pub multiple: bool,
pub required: bool,
pub max_length: Option<u32>,
pub min: Option<&'a str>,
pub max: Option<&'a str>,
pub step: Option<&'a str>,
pub extended: bool,
}Expand description
One field of a form.
Borrowed rather than owned: a description is built, read once by a renderer, and dropped. Nothing here outlives the screen it describes.
§What it carries, and what it does not
Stated here so the next renderer does not re-ask, which is what the first two both did. It carries everything a renderer needs to draw the field: its kind, what it is called, what it is asked for, its standing help, what is wrong with it now, whether it is compulsory, whether it hides behind a disclosure, its ghost text, and the options it offers.
It does not carry the current value, and it is not going to. That is the
one thing here that is genuinely renderer state: a webview reads it back out
of the DOM, an immediate-mode renderer holds a &mut to the app’s own field
and writes through it, and a terminal keeps an edit buffer. A description
that carried the value would have to carry a way to write it back, at which
point it is a form model and no longer a description.
Constraints are here and enforcement is not, which is one line rather
than two. required, max_length, min and max are facts about
the question, so a renderer can emit its host’s idiom for each — an HTML
attribute, a marked label, a clamped spinner — and the platform helps the
user before anything is submitted. Deciding that a value is wrong stays with
whoever validated, and error is that decision arriving back.
The set stops before pattern, and stops there on both tests at once. A
regex has an honest answer in a webview and none anywhere else: egui would
have to run it per keystroke and decide what a half-typed value means, which
is enforcement wearing description’s clothes. And it is one site in goingson
and none in Balanced Breakfast, against 8 and 1 for maxlength. Measured
2026-08-09, 2cbad3e2.
Fields§
§kind: FieldKindWhat kind of value it takes.
name: &'a strThe name the value is submitted under.
label: &'a strWhat the user is asked for.
hint: Option<&'a str>Standing help, shown whether or not anything is wrong.
error: Option<&'a str>What is currently wrong with the value.
placeholder: Option<&'a str>Ghost text shown while the field is empty.
User-facing text, and it sits with label and hint rather than with
the value because it is a property of the question and not of the
answer. It lived renderer-side in makeover-webview until 0.8.0 for one
reason and it was not a reading on where it belonged: adding a field to
a published struct is a breaking change.
Not a substitute for a label. A field labelled only by its placeholder loses its label the moment anything is typed, and no renderer here can make that not happen, so the description keeps both.
options: &'a [Choice<'a>]The options offered, in the order they are offered.
Empty for every kind FieldKind::offers_options rejects. A field
described with no options is sayable on purpose: it is what an app with
an unfinished-loading option list actually has, and a renderer showing
an empty control says so on screen rather than in a log.
Which option is current is not here. That is the value, and the value is renderer state.
accept: &'a [Accepted<'a>]What a file field takes, in the order a host offering the list shows it.
Empty for every kind FieldKind::takes_files rejects, and empty is
also a real answer for one that accepts it: a field that takes any file
says so by listing nothing, which is what an <input type="file"> with
no accept does and what most of the measured sites are.
It is a filter and it is the disclosure cue, and Accepted’s doc
carries which reading is which. Nothing here validates: a host may hand
back a file the list does not cover, exactly as a browser does when the
user switches the picker to “All Files”, and deciding a value is wrong
stays with whoever validated.
Added 0.31.0, f7261a5a.
multiple: boolWhether more than one file may be picked at once.
Only FieldKind::takes_files reads it. A multi-valued answer to any
other question is a different shape — a set of options, a repeated
group — and neither is this flag with a different kind beside it.
False is the common case: 4 of the MNW server’s 16 file inputs carry it.
Added 0.31.0, f7261a5a.
required: boolWhether the form refuses to submit without it.
max_length: Option<u32>The longest the value may be, in characters.
Added 0.11.0 with min and max, joining
required, which had been the only constraint here
since before the crate wrote down that it carried none.
min: Option<&'a str>The lowest value accepted, as the host would write it.
Text rather than a number, because the bound is only a number for some
of the kinds that take one. goingson’s own sites are min="1" on a
duration and min="2026-08-09T14:30" on a datetime, and a numeric member
could say the first and not the second. The kind already
says how to read it, the same way it does for the value.
max: Option<&'a str>The highest value accepted, as the host would write it. See
min.
step: Option<&'a str>The granularity the value moves in, as the host would write it.
Text for min’s reason, and it earns it twice over: the
step of a date is a day and the step of a threshold is 0.01, and a
numeric member could say one of them.
Absent means the host’s own granularity, which is the honest default
rather than a missing value: a webview’s <input> steps by 1 unless told
otherwise, and that is the browser’s rule and not this crate’s to
restate. It matters most to FieldKind::Range, where the host default
turns a 0-to-1 threshold into a two-position control, and it is not
exclusive to it: a stepped Number is the same fact
about a typed value.
Added 0.28.0 with FieldKind::Range.
extended: boolWhether the field lives behind a “more options” disclosure.
Implementations§
Source§impl<'a> Field<'a>
impl<'a> Field<'a>
Sourcepub const fn new(kind: FieldKind, name: &'a str, label: &'a str) -> Self
pub const fn new(kind: FieldKind, name: &'a str, label: &'a str) -> Self
A plain required-nothing field of the given kind.
Sourcepub const fn range(
name: &'a str,
label: &'a str,
min: &'a str,
max: &'a str,
) -> Self
pub const fn range( name: &'a str, label: &'a str, min: &'a str, max: &'a str, ) -> Self
A bounded number the user drags across its whole extent.
The third under-described kind, and it gets a constructor for
select’s reason: a range is the one kind whose bounds
are not a rule but the control itself, so a call site that forgot them
has a slider with nothing to slide across. Taking them as arguments is
what makes that unsayable.
step stays a field rather than a fourth argument. It is
genuinely optional — the host’s granularity is a real answer — and the
two bounds are not.
Sourcepub const fn upload(
name: &'a str,
label: &'a str,
accept: &'a [Accepted<'a>],
) -> Self
pub const fn upload( name: &'a str, label: &'a str, accept: &'a [Accepted<'a>], ) -> Self
A file field, taking the given accept list.
The fourth under-described kind and it gets a constructor for
range’s reason rather than select’s:
a file field with no accept list is not broken, it is a field that takes
anything, and the hazard is the opposite one. A call site that meant to
restrict and forgot has a picker offering every file on the machine and
a server refusing the upload afterwards, which is the failure the list
exists to move forward. Taking it as an argument is what makes an
accidental omission a deliberate &[].
multiple stays a field. One file is the common case
and the honest default; several is the thing worth saying.
Sourcepub const fn select(
name: &'a str,
label: &'a str,
options: &'a [Choice<'a>],
) -> Self
pub const fn select( name: &'a str, label: &'a str, options: &'a [Choice<'a>], ) -> Self
A select offering the given options.
One of the two kinds under-described by Field::new, so it gets a
constructor rather than leaving every call site to remember that a
select with an empty options renders as an empty select.
Sourcepub const fn radio(
name: &'a str,
label: &'a str,
options: &'a [Choice<'a>],
) -> Self
pub const fn radio( name: &'a str, label: &'a str, options: &'a [Choice<'a>], ) -> Self
A radio group offering the given options.
The other. Same hazard as select and a worse one: a
radio group with no options draws nothing at all, so a call site that
forgot them has an empty rectangle rather than a visibly empty control.
Sourcepub const fn invalid(&self) -> bool
pub const fn invalid(&self) -> bool
Whether the field is currently reporting a problem.
Read this rather than testing error.is_some() at each renderer: the
error state has to mark the field’s whole group and not only the
message, because a renderer with no descendant selectors (egui, a
terminal) cannot find the group from the message. goingson already marks
the group and Balanced Breakfast does not, so goingson’s shape is the
one taken here.
Sourcepub const fn bounded(&self) -> bool
pub const fn bounded(&self) -> bool
Whether the field carries both ends of its extent.
Only FieldKind::Range owes them, and it owes them absolutely: a
slider with one end missing has no extent to draw. Named here rather
than left to each renderer to test min.is_some() && max.is_some(),
which is three renderers arriving at the same condition and one of them
getting it wrong, and named as a question about the field rather than
about the kind because the kind cannot see the bounds.
It is a check and not a guarantee. Nothing here refuses to build an
unbounded range — Field::range is what makes the bounded one easy —
so a renderer asks this and falls back to whatever its host does
honestly with a number.
Sourcepub fn accepts_media(&self) -> bool
pub fn accepts_media(&self) -> bool
Whether anything in accept names a media family.
The question a renderer asks before it decides to keep room for a
preview, and it is deliberately the whole list rather than one entry:
the media dropzone this was measured against takes image/*,video/*, so
there is no single family to return and there is still a disclosure to
offer. Which one it turns out to be is known once a file is picked, which
is renderer-side and after the description is gone.
False for an empty list, for a list of suffixes, and for text/csv. A
renderer that wants the family of a particular entry reads
Accepted::family.