Skip to content

Forms

Every form field on the site — account pages, addresses, the storefront password gate — is styled by one file: frontend/entrypoints/css/blocks/forms.css. Add the class, get the brand style. There is no per-section form styling.

The look matches the newsletter input in the footer (Figma node 2994:8516): a white pill with a 1px Leaf border and Deep Green text.

Class Use on
.form-control Any input, select or textarea
.form-label The <label> above a field
.form-hint Helper text below a field
.form-error An inline error message below a field
.form-checkbox input[type="checkbox"]
.form-radio input[type="radio"]
.form-alert + .form-alert-error Error banner above a form
.form-alert + .form-alert-success Success banner above a form

A minimal field:

<label for="CustomerEmail" class="form-label">Email</label>
<input type="email" id="CustomerEmail" name="customer[email]" class="form-control" required>

.form-control handles input, select and textarea from the same class — selects get a chevron and the padding to clear it, textareas swap the pill radius for a rounded rectangle (fully rounded ends clip the first and last lines of text).

  • 50px tall, matching the design, from padding rather than a fixed height — so the field grows with its content instead of clipping.
  • Focus: a 2px Deep Green outline at 2px offset, plus the border darkening to Deep Green. Uses outline, not box-shadow, so it survives Windows High Contrast mode.
  • Disabled: muted Leaf tints and cursor-not-allowed.
  • Invalid: a 2px Deep Green border, triggered by aria-invalid="true".

Set aria-invalid and point the field at its message:

<input
type="email"
id="CustomerEmail"
class="form-control"
aria-invalid="true"
aria-describedby="CustomerEmail-error"
>
<span class="form-error" id="CustomerEmail-error">Enter a valid email address.</span>

Form-level banners go above the form and need a role so they are announced:

{% if form.errors %}
<div class="form-alert form-alert-error" role="alert">
<p>{{ form.errors | default_errors }}</p>
</div>
{% endif %}

Use role="alert" for errors and role="status" for success messages.

These are not optional extras — they are why the classes look the way they do.

Errors are never signalled by colour alone. .form-error and the alert banners each carry a leading glyph (! for errors, for success) so the two are distinguishable without colour vision. The glyphs are pseudo-elements, so they stay out of the accessible name; the message itself is announced through role and aria-describedby.

The invalid border is Deep Green, not Soft Coral. Soft Coral measures 1.45:1 against a white fill — well under the 3:1 WCAG 2.2 AA floor for non-text UI — so as a border it is invisible to low-vision users. It is used as a background wash behind the error text instead, where the contrast requirement falls on the Deep Green text over it (9.9:1). The invalid state is carried by border thickness rather than hue.

Checkboxes and radios have a 44px tap target. The visible box is 20px, per the design; a transparent pseudo-element extends the hit area to 44px without changing the look.

Some Shopify form objects emit a bare <input> you cannot add a class to — form.set_as_default_checkbox on the address forms is the one in this theme. Mark the wrapper instead:

<label class="flex items-center gap-3" data-shopify-checkbox>
{{ form.set_as_default_checkbox }}
<span class="text-body-sm text-deep-green">Set as default address</span>
</label>

[data-shopify-checkbox] input[type="checkbox"] picks up the same styling.

The style guide renders every field type and state — including focus, invalid, disabled, hint text and both alert banners — at /pages/contact-us?view=style-guide.