Connection lost. Reconnecting… attempt 1 of 8
Paused. Your work is held on the server.
Could not reconnect.
This session has expired on the server.
An unhandled error has occurred.
Sedna.UI
v0.17.0 · main

Validation

tier 2 — classes

The error border keys off aria-invalid, never a class, so the styling and what a screen reader announces cannot disagree. Point aria-describedby at the message and it is read as part of the field.

Error and warning

.form-error is a rejected value. .form-warning is one that is accepted but unwise — it carries no aria-invalid, because the field is valid.

Not a complete address — nothing would be delivered.

Beyond the 365-day archive window, so the tail cannot be restored.

<div style="max-width:460px; display:flex; flex-direction:column; gap:16px">
    <div class="form-field">
        <label class="form-label form-label--required" for="f-mail">Notification address</label>
        <input class="form-input" id="f-mail" type="email" required
               aria-invalid="true" aria-describedby="f-mail-err" value="ops.example" />
        <p class="form-error" id="f-mail-err">
            <i class="ri-error-warning-line"></i>
            <span>Not a complete address — nothing would be delivered.</span>
        </p>
    </div>
    <div class="form-field">
        <label class="form-label" for="f-retain">Retention</label>
        <input class="form-input" id="f-retain" type="text" value="400 days"
               aria-describedby="f-retain-warn" />
        <p class="form-warning" id="f-retain-warn">
            <i class="ri-alert-line"></i>
            <span>Beyond the 365-day archive window, so the tail cannot be restored.</span>
        </p>
    </div>
</div>

Across every control

One attribute, the same result on an input, a select and a text area. The select is the case worth knowing: it needs an empty first option for required to bite at all.

Not a complete address — nothing would be delivered.

Pick a team, or nobody is paged.

An override is recorded against your name, so it needs a reason.

<div class="sedna-col" style="max-width:520px; gap:16px">
    <div class="form-field">
        <label class="form-label form-label--required" for="av-mail">Notification address</label>
        <input class="form-input" id="av-mail" type="email" required
               aria-invalid="true" aria-describedby="av-mail-err" value="ops.example" />
        <p class="form-error" id="av-mail-err">
            <i class="ri-error-warning-line"></i>
            <span>Not a complete address — nothing would be delivered.</span>
        </p>
    </div>
    <div class="form-field">
        <label class="form-label form-label--required" for="av-team">Escalation team</label>
        <select class="form-input form-select" id="av-team" required
                aria-invalid="true" aria-describedby="av-team-err">
            <option value="" selected>Choose a team…</option>
            <option value="fulfilment">Fulfilment</option>
            <option value="network">Network</option>
        </select>
        <p class="form-error" id="av-team-err">
            <i class="ri-error-warning-line"></i>
            <span>Pick a team, or nobody is paged.</span>
        </p>
    </div>
    <div class="form-field">
        <label class="form-label form-label--required" for="av-reason">Reason</label>
        <textarea class="form-input" id="av-reason" rows="2" required
                  aria-invalid="true" aria-describedby="av-reason-err"></textarea>
        <p class="form-error" id="av-reason-err">
            <i class="ri-error-warning-line"></i>
            <span>An override is recorded against your name, so it needs a reason.</span>
        </p>
    </div>
</div>

Marking a field required

.form-label--required draws the marker and required on the control does the enforcing. Write both: the marker is decoration, and a screen reader is told by the attribute.

Marked and enforced: the label carries the marker, the control carries required.

No marker, no attribute. The two always agree.

<div class="sedna-col" style="max-width:460px; gap:16px">
    <div class="form-field">
        <label class="form-label form-label--required" for="rq-host">Hostname</label>
        <input class="form-input" id="rq-host" type="text" required value="exch-02.example.com" />
        <p class="form-hint">Marked and enforced: the label carries the marker, the control carries <code>required</code>.</p>
    </div>
    <div class="form-field">
        <label class="form-label" for="rq-owner">Owner</label>
        <input class="form-input" id="rq-owner" type="text" placeholder="Optional" />
        <p class="form-hint">No marker, no attribute. The two always agree.</p>
    </div>
</div>

Inside a two-up row

The message belongs to the field, so it sits under that field's control and not under the row; the .sedna-grid keeps the two columns aligned at the top. A form that rejected more than one field also says so once, at the top, in a validation summary linking to each.

Above 65535, so no port can answer.

Notes are shown to every operator, including contractors.

<form style="max-width:560px">
    <div class="sedna-grid sedna-grid--sm sedna-grid--fit">
        <div class="form-field">
            <label class="form-label form-label--required" for="vg-host">Host</label>
            <input class="form-input" id="vg-host" type="text" value="src-db-14" required />
        </div>
        <div class="form-field">
            <label class="form-label form-label--required" for="vg-port">Port</label>
            <input class="form-input" id="vg-port" type="number" value="65900" required
                   aria-invalid="true" aria-describedby="vg-port-err" />
            <p class="form-error" id="vg-port-err">
                <i class="ri-error-warning-line"></i>
                <span>Above 65535, so no port can answer.</span>
            </p>
        </div>
        <div class="form-field sedna-span-full">
            <label class="form-label" for="vg-note">Note</label>
            <textarea class="form-input" id="vg-note" rows="2" aria-describedby="vg-note-warn">Temporary — remove after the migration</textarea>
            <p class="form-warning" id="vg-note-warn">
                <i class="ri-alert-line"></i>
                <span>Notes are shown to every operator, including contractors.</span>
            </p>
        </div>
    </div>
    <div class="form-actions">
        <button class="btn" type="button">Cancel</button>
        <button class="btn btn-primary" type="button">Save connection</button>
    </div>
</form>