Forms & validation

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.

Fields

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.

Note

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).

Used only to confirm your submission.

Give a reason. It appears publicly on the abstract page.

Relevant code
<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>
.ds-form
Wraps the fields. Half-width fields share a line, a full-width one takes its own, and anything that is not a field takes a whole row. Optional: a plain <form> stacks every field.
.ds-field
Wraps one label, control, hint and message, in that order, and supplies the spacing between fields.
.ds-label
The label. Always present, always above the control, and always tied to it with for.
.label-optional
On the label of an optional field, holding the word (optional). Required fields carry no marker; they take required and aria-required="true" on the control instead.
.ds-input
The control. Goes on input, select and textarea alike.
.ds-hint
Help text, after the control. Give it an id and name it in the control's aria-describedby. Say what good input looks like before someone gets it wrong.
.ds-select
Optional wrapper around a <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.
.ds-check
A checkbox or radio row: a <label> holding the native control and a <span> with the text. The whole label is the target.

Validation

Fields can be in three possible states: normal, validated, or disabled. Validation styles change based on the error disposition, that is, its severity.

Normal

One sentence is enough.

Disabled

Locked once a paper is announced.

Validated · error

Remove the HTML markup <br>.

Validated · warning

This does not look like an ACM code. Expected form: F.2.2.

Validated · info

Extra spaces were removed. Please confirm it reads correctly.

Validated · multiple problems
  1. Do not begin the abstract with the word “Abstract”.
  2. Remove the HTML markup <br>.
  3. Font commands are not processed and appear literally.
  4. Add a space between a URL and the punctuation after it.
Relevant code
<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>
.is-invalid
On the control, for the error tier. Pair with aria-invalid="true".
.is-warning
On the control, for the warning tier. No aria-invalid: the value is accepted.
.is-info
On the control, for the info tier: arXiv changed the value and is telling the author. No aria-invalid.
.field-error, .field-warning, .field-info
The message, after the control, one class per tier. Needs an id that the control's aria-describedby names. Keep it hidden until it applies.
.field-messages
An <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.
[disabled]
On the control. The label and hint stay; the control greys out and leaves the tab order. For a control that could explain why it is unavailable, prefer aria-disabled="true"; see the accessibility essentials.

Alerts above the form

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.

Relevant code
<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--error, .ds-alert--warning, .ds-alert
One alert per severity, never a mixed one: red has to mean “you cannot proceed”, and a non-blocking item inside a red alert destroys that. Two problems of two severities means two alerts, in severity order. A warning summary is the same construction in .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.
.ds-alert-title
Carries the count. The body under it says what that severity means for the reader: whether they may continue.
<ol>
One row per problem, each an <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.

Tone

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.

  • Error. Say what to do, not what went wrong. Never blame the reader, never use invalid on its own, and never make someone guess which of eight rules they broke.
  • Warning. Say what happens if they continue, because they can. “Your submission may be held for moderator review” is the message; “check this field” is not.
  • Info. Say what changed and ask them to confirm it. This message exists because arXiv edited the author’s content, so it reports rather than instructs, and it never implies fault.
Instead ofWrite
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.

Switch

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.

Public
Internal
Disabled
Relevant code
<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>
.ds-switch
The <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.
role="switch"
On the checkbox input. With it a screen reader says “on” and “off”; without it, “checkbox, checked”. The input stays a real checkbox, so the keyboard behaviour, the state and the form value are native and it works with no JavaScript.
.ds-switch-track, .ds-switch-thumb
The drawn track and the thumb that moves along it. Both required, nested as shown, directly after the input.
.ds-switch-label
The visible text. Its colour follows the state; nothing else about it changes.
--ds-switch-on
The on-track colour: the darker end of the accent, never the accent itself, so the white thumb keeps its contrast. The internal stylesheet re-points this one token and changes nothing else.
[disabled]
On the input. The track and the label grey out together.

Segmented control

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.

Note

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.

Positive
Positive example
Neutral
Neutral example
Negative
Negative example
Undecided
Undecided example
Interactive
Demo decision
Relevant code
<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>
.ds-seg
A <fieldset> holding two to four .ds-seg-btn.
<legend>
Required. It is the group's accessible name: without it a screen reader announces three buttons and never says what they decide. Hide it with .is-sr-only when the surrounding text already says it; hide it, do not omit it.
.ds-seg-btn
One option, a <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.
.ds-seg-btn--positive, .ds-seg-btn--negative
Accept, informational, reject. They take the success, info and error tokens and mean the same thing those mean everywhere else; they are not free colours to pick from, and they flip for dark mode with the rest of the status palette.
.is-active
The selected option. Set by script when an option is clicked, and removed from the others; the stylesheet gives the buttons no behaviour.

Tooltip

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.

Note

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.

If the paper has been published, the publisher's DOI. Leave it empty otherwise; arXiv mints its own.
Anchored at the trailing edge With .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.

Relevant code
<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>
.ds-tooltip-host
Wraps the trigger and the bubble, and positions the bubble. It sits beside the <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.
aria-describedby="tip-doi"
On the trigger, naming the tooltip's 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.
.ds-tooltip
The bubble, with 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.
.ds-tooltip-body
The text. Short, and never content that exists nowhere else.
.ds-tooltip--end
For a host near the trailing edge of its container: the bubble runs back toward the start instead of off screen. Both directions use logical properties, so both are correct in a right-to-left script.

Stepping through a set

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.

Note

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.

Relevant code
<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>
.ds-pagination
A <nav> with an aria-label saying which set it steps through. Holds Previous, the position and Next, in that order.
.ds-pagination-position
The counter. Takes 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.
.ds-btn
The two buttons. At either end of the set the button is 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.

Accessibility essentials

  • Every control has a label. Put a real <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.
  • Mark the optional fields, not the required ones. Put (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.
  • Connect every hint and message to its control. Give each .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.
  • Use 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.
  • Say the tier in words. Colour and a border are never the only signal (WCAG 1.4.1). The message says what is wrong or what changed, or carries a visually hidden “Error:” or “Warning:” prefix.
  • Validate on submit, then move focus. Do not tell someone they are wrong while they are still typing. Once a field is invalid, re-check it on every input event so the error clears the moment it is fixed. When submission fails, move focus to the first invalid field.
  • Give the summary its role. The alert above the form takes role="alert" for errors and warnings, so a screen reader announces it at once, and every row in it links to its field.
  • Prefer 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.

Modifiers

Field width

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.

Relevant code
<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>
.ds-field--short
Caps the field at 11rem, for a code, an identifier or a year.
.ds-field--full
Releases the cap, for something that genuinely wants the whole measure.
.ds-field--sm, .ds-field--md, .ds-field--lg
Internal tools only, in 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.

Rules

Validation tiers

What each tier means for the person filling the form in.

DispositionCan they continue?What it means
ErrorNoThe value is not acceptable and must be corrected.
WarningYesAccepted, but the submission may be held for moderator review.
InfoYesarXiv 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.

Layout