How arXiv builds a form, and how it tells someone what is wrong with one. The same markup works in internal tools and on the public site; only the accent color differs.
A field is a label, a control, optional help text, and a slot for a message. That order never changes. Labels sit above their controls, and related fields group by spacing rather than boxes.
Field widths are set by the type of control, on the assumption that the element already tells you how much someone will type, and two classes override that where the content is unusually short or unusually long (see Field width, below).
<form class="ds-form">
<div class="ds-field">
<label class="ds-label" for="f-email">Email address</label>
<input class="ds-input" id="f-email" type="email" required aria-required="true" aria-describedby="f-email-hint" value="e.vasquez@example.edu">
<p class="ds-hint" id="f-email-hint">Used only to confirm your submission.</p>
</div>
<div class="ds-field">
<label class="ds-label" for="f-cat">Primary category</label>
<select class="ds-input" id="f-cat" required aria-required="true">
<option>hep-th — High Energy Physics – Theory</option>
<option>math.CO — Combinatorics</option>
<option>physics.optics — Optics</option>
</select>
</div>
<div class="ds-field ds-field--short">
<label class="ds-label" for="f-orcid">ORCID iD<span class="label-optional">(optional)</span></label>
<input class="ds-input" id="f-orcid" type="text" placeholder="0000-0000-0000-0000">
</div>
<label class="ds-check">
<input type="checkbox" checked>
<span>Email me when the moderators respond</span>
</label>
<div class="ds-btn-group ds-btn-group--end">
<button class="ds-btn ds-btn-text" type="button">Cancel</button>
<button class="ds-btn ds-btn-primary" type="submit">Save changes</button>
</div>
</form>
<form> stacks every field.for.(optional). Required fields carry no marker; they take
required and aria-required="true" on the control instead.input, select and
textarea alike.id and name it in the
control's aria-describedby. Say what good input looks like before
someone gets it wrong.<select class="ds-input"> that swaps the
native arrow for the drawn chevron. Not used above: the native arrow is right in dark
mode, at every zoom and in forced colors, so leave it unless there is a reason.<label> holding the native control and
a <span> with the text. The whole label is the target.Fields can be in three possible states: normal, validated, or disabled. Validation styles change based on the error disposition, that is, its severity.
One sentence is enough.
Locked once a paper is announced.
Remove the HTML markup <br>.
This does not look like an ACM code. Expected form: F.2.2.
Extra spaces were removed. Please confirm it reads correctly.
<div class="ds-field ds-field--full">
<label class="ds-label" for="v3">Abstract</label>
<input class="ds-input is-invalid" id="v3" type="text" aria-invalid="true" aria-describedby="v3-m" value="<br>Our analysis introduces">
<p class="field-error" id="v3-m">Remove the HTML markup <code><br></code>.</p>
</div>
<div class="ds-field ds-field--full">
<label class="ds-label" for="v4">ACM classification<span class="label-optional">(optional)</span></label>
<input class="ds-input is-warning" id="v4" type="text" aria-describedby="v4-m" value="s 25">
<p class="field-warning" id="v4-m">This does not look like an ACM code. Expected form: <code>F.2.2</code>.</p>
</div>
<div class="ds-field ds-field--full">
<label class="ds-label" for="m1">Abstract</label>
<textarea class="ds-input is-invalid" id="m1" rows="3" aria-invalid="true" aria-describedby="m1-m">Abstract: We study the convergence of stochastic gradient methods.<br>Data at https://example.org/sgd.</textarea>
<ol class="field-messages" id="m1-m">
<li class="field-error">Do not begin the abstract with the word “Abstract”.</li>
<li class="field-error">Remove the HTML markup <code><br></code>.</li>
<li class="field-warning">Font commands are not processed and appear literally.</li>
<li class="field-warning">Add a space between a URL and the punctuation after it.</li>
</ol>
</div>
aria-invalid="true".aria-invalid: the value is
accepted.aria-invalid.id that the control's aria-describedby names. Keep it
hidden until it applies.<ol> when one field carries several problems. Each
<li> takes its own tier class, and the list takes the id
the control points at. The control takes the class of the worst tier in the list.aria-disabled="true"; see the accessibility essentials.On a form of any length, repeat the problems above the first field. This
is not a new component: it is .ds-alert, specified
on its own page, holding an ordered list of links. What follows is only what is particular to
using one for validation.
2 blocking errors
Blocking errors must be corrected before you can continue. Select an item to jump to that field.
<div class="ds-alert ds-alert--error" role="alert">
<svg class="ds-alert-icon" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none" aria-hidden="true">
<circle cx="12" cy="12" r="10"/>
<path d="m15 9-6 6"/>
<path d="m9 9 6 6"/>
</svg>
<div class="ds-alert-content">
<p class="ds-alert-title">2 blocking errors</p>
<p>Blocking errors must be corrected before you can continue. Select an item to jump to that field.</p>
<ol>
<li><a href="#m1">Abstract — do not begin with the word “Abstract”</a></li>
<li><a href="#m1">Abstract — remove the HTML markup <code><br></code></a></li>
</ol>
</div>
</div>
.ds-alert--warning, and an automatic change is a bare .ds-alert, which is the info state.
Takes role="alert" for error and warning, role="status" for
info. See Alerts for the four severities and their markup.<a> linking to its field. Rows repeat
the field name even when several name the same field: without it the reader cannot tell
whether three problems mean one field to visit or three. Derive every count and every row
from the field results; a summary written by hand can disagree with the fields it
describes, and eventually will.The general rules — no contractions, one instruction per sentence, the same word for the same thing every time, no idiom in text someone reads while something is going wrong — live in STYLE.md and are not repeated here. What follows is specific to validation messages.
| Instead of | Write |
|---|---|
| Invalid email. | Enter an email address, like name@university.edu. |
| This field is required. | Give a reason before saving. |
| Password does not meet requirements. | Use at least 12 characters. |
| Check this field. | You may continue, but your submission may be delayed. |
| Title was invalid. | Extra spaces were removed. Please confirm it reads correctly. |
A boolean that applies the moment it is flipped. If the change needs a Save step, it is a checkbox in a form and not this. That is the whole distinction, and it is about when the change lands rather than how the control is built.
<label class="ds-switch">
<input type="checkbox" role="switch" checked>
<span class="ds-switch-track"><span class="ds-switch-thumb"></span></span>
<span class="ds-switch-label">Email digest</span>
</label>
<label> that wraps the whole control. The visible text goes
inside it, because that is what names the switch; a switch with only a heading beside it
has no accessible name.An exclusive choice rendered as adjacent buttons, for a decision where
every option should stay visible. Two to four options; past that it is a <select>
or a radio group, because a row of six buttons stops being scannable and starts being a wall.
Nothing is selected until something is selected: the undecided example is the resting state of a decision nobody has made yet, and it is a real state, not a bug to hide by pre-selecting the middle option.
<fieldset class="ds-seg">
<legend class="is-sr-only">Positive example</legend>
<button type="button" class="ds-seg-btn ds-seg-btn--positive is-active">Author</button>
<button type="button" class="ds-seg-btn">Proxy</button>
<button type="button" class="ds-seg-btn ds-seg-btn--negative">No</button>
</fieldset>
<fieldset> holding two to four .ds-seg-btn..is-sr-only
when the surrounding text already says it; hide it, do not omit it.<button type="button">. Buttons and not a radio
group on purpose: this is for a decision that acts when clicked. A choice that is part
of a form that gets submitted is a radio group.A short explanation attached to a control, on hover and on focus. The lighter half of the popover family: no title, no close button, and never content that exists nowhere else.
Not for anything required: if the reader must have it to fill the field in, it is hint text under the label, not a tooltip, which is for the person who stops and wonders.
.ds-tooltip--end the bubble runs back
toward the start, so a control near the trailing edge does not push it off screen.
Hover or tab to either question mark.
<div class="ds-field ds-field--short">
<div>
<label class="ds-label" for="f-doi">DOI</label>
<span class="ds-tooltip-host">
<button type="button" class="ds-btn ds-btn-text ds-btn-icon" aria-describedby="tip-doi">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<circle cx="12" cy="12" r="10"/>
<path d="M9.1 9a3 3 0 0 1 5.8 1c0 2-3 3-3 3"/>
<path d="M12 17h.01"/>
</svg>
<span class="is-sr-only">What is a DOI?</span>
</button>
<span class="ds-tooltip" id="tip-doi" role="tooltip">
<span class="ds-tooltip-body">If the paper has been published, the publisher's DOI. Leave it empty otherwise; arXiv mints its own.</span>
</span>
</span>
</div>
<input class="ds-input" id="f-doi" type="text" placeholder="10.1000/example">
</div>
<label>, never inside it: a label forwards clicks to the control it
names, so a button nested in one is a button whose clicks land somewhere else.id. The tooltip describes the
control and is never its accessible name: a name that only appears on hover is a name
most people never get, so the trigger has an .is-sr-only label of its own,
as above.role="tooltip" and the id. It opens
downward and toward the inline end, on hover and on focus, with no JavaScript. Escape
must close it without moving focus, which CSS cannot do: a listener sets
hidden on it, and clears it when the pointer or focus arrives again. The
script on this page is the reference.Moving through items one at a time, such as a moderation queue or a run of submissions, where each item has its own screen and someone works through them in order.
Not numbered pages: a stepper answers “what is next”, a row of page numbers answers “take me to item 40”, and if a surface ever needs the second it is a different component and not a variant of this one.
<nav class="ds-pagination" aria-label="Queue navigation">
<button class="ds-btn" type="button" disabled><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 18-6-6 6-6"/></svg> Previous</button>
<span class="ds-pagination-position" aria-live="polite">Request 1 of 15</span>
<button class="ds-btn" type="button">Next <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6"/></svg></button>
</nav>
<nav> with an aria-label saying which set it steps
through. Holds Previous, the position and Next, in that order.aria-live="polite": without it a reader who cannot
see the counter has no way to know that the Next they just pressed did anything. The
wording is the host's to choose; the counter takes the slack between the buttons, so the
buttons stay put as the number grows.disabled, not
removed: a control that vanishes changes the shape of the toolbar under the reader, and
a disabled one says “there is nothing before this”, which is the actual information.<label for> on
every control, pointing at the control's id. Hide it with
.is-sr-only when there is no room for it; do not leave it out.(optional) inside the visible label of an optional field. Put
required and aria-required="true" on every required control: required
fields carry no visual marker, so this pair is the only signal a screen reader gets. Never put
either word in aria-label or title..ds-hint and each message an id, and list them in the control's
aria-describedby. Without that a screen reader announces the field and nothing
else.aria-invalid for errors only. Set
aria-invalid="true" on a control in the error tier and remove it when the error
clears. A warning is not invalid, and an info message is not a problem at all.input
event so the error clears the moment it is fixed. When submission fails, move focus to the
first invalid field.role="alert" for errors and warnings, so a screen reader announces it at once,
and every row in it links to its field.aria-disabled to disabled for a control that could
explain itself. disabled leaves the tab order entirely, so a user who
cannot proceed finds nothing there and no reason why. The two must look identical.Width follows the content. A field the width of a postcode says what is
expected before anyone types, and stretching every control edge-to-edge throws that clue away. Most
of this happens without asking: the stylesheet sizes a field from its input type, so
a date, a time or a number is already narrow. Two overrides exist for the case the element cannot
infer, because type="text" covers a four-digit year and a paper title alike.
<form class="ds-form">
<div class="ds-field ds-field--short">
<label class="ds-label" for="w-year">Year</label>
<input class="ds-input" id="w-year" type="text" inputmode="numeric" value="2026">
</div>
<div class="ds-field">
<label class="ds-label" for="w-date">Date</label>
<input class="ds-input" id="w-date" type="date" value="2026-09-23">
</div>
<div class="ds-field ds-field--full">
<label class="ds-label" for="w-title">Title</label>
<input class="ds-input" id="w-title" type="text" value="Heavy-tailed gradients in deep networks: a study of convergence under stochastic optimisation">
</div>
</form>
internal-tools.css: caps the control at
240px, 360px or 480px. Internal screens are dense enough to need field widths chosen
rather than inferred, so on an internal form a person picks the size instead of the
input type picking it. Use the pair above on public pages and this trio on
internal pages, and do not mix them on one form.What each tier means for the person filling the form in.
| Disposition | Can they continue? | What it means |
|---|---|---|
| Error | No | The value is not acceptable and must be corrected. |
| Warning | Yes | Accepted, but the submission may be held for moderator review. |
| Info | Yes | arXiv changed the value automatically. Nothing is wrong; the author is being told. |
The info disposition is what makes automatic correction legitimate: normalising whitespace or stripping markup is an edit to author-submitted content, and what makes that acceptable is that the author sees the change before it is committed. The review step is not a nicety to drop later for “obvious” fixes.
type, and two classes cover the rest; see
Field width, above.