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.31.0 · main

Validation

tier 2 — classes

A field is at rest, rejected or unusual. Rejected keys off aria-invalid, never a class, so the styling and what a screen reader announces cannot disagree; unusual is data-state="warn". Both go on the control, every field family draws both, and aria-describedby on the control makes the message part of the field.

Error and warning

.form-error is a rejected value. .form-warning is one that is accepted but unwise — its field carries data-state="warn" and no aria-invalid, because the value is kept.

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"
               data-state="warn" 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>

Unusual, not rejected

data-state="warn" is for a value that is saved but worth a second look — outside the usual range, far from the average. Red would tell the reader it was refused. No ARIA attribute says "unusual", so the .form-warning tied by aria-describedby is what a screen reader hears; write it every time. On an .input-group the attribute still goes on the control, and the group draws the edge.

days

Usually 30 to 365. Saved, but the archive only reaches back 365 days.

This customer has never been shipped to from AP-South.

EUR

Four times the average of the last 90 days.

<div class="sedna-col" style="max-width:520px; gap:16px">
    <div class="form-field">
        <label class="form-label" for="un-retain">Retention</label>
        <div class="input-group">
            <input class="form-input" id="un-retain" type="number" value="400"
                   data-state="warn" aria-describedby="un-retain-warn" />
            <span class="input-affix">days</span>
        </div>
        <p class="form-warning" id="un-retain-warn">
            <i class="ri-alert-line"></i>
            <span>Usually 30 to 365. Saved, but the archive only reaches back 365 days.</span>
        </p>
    </div>
    <div class="form-field">
        <label class="form-label" for="un-region">Shipping region</label>
        <select class="form-select" id="un-region" data-state="warn" aria-describedby="un-region-warn">
            <option>EU-West</option>
            <option selected>AP-South</option>
        </select>
        <p class="form-warning" id="un-region-warn">
            <i class="ri-alert-line"></i>
            <span>This customer has never been shipped to from AP-South.</span>
        </p>
    </div>
    <div class="form-field">
        <label class="form-label" for="un-price">Unit price</label>
        <div class="input-group input-group--sm">
            <span class="input-affix">EUR</span>
            <input class="form-input" id="un-price" type="text" inputmode="decimal" value="1,240.00"
                   data-state="warn" aria-describedby="un-price-warn" />
        </div>
        <p class="form-warning" id="un-price-warn">
            <i class="ri-alert-line"></i>
            <span>Four times the average of the last 90 days.</span>
        </p>
    </div>
</div>

Across every control

One attribute, the same result on an input, a select, an .input-group and a text area — and on a combo box, a colour field and both editors. In a group, the attribute stays on the control and the group draws the edge. 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.

pallets

At least one pallet, or there is nothing to dispatch.

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-qty">Quantity</label>
        <div class="input-group">
            <input class="form-input" id="av-qty" type="number" value="0" min="1" required
                   aria-invalid="true" aria-describedby="av-qty-err" />
            <span class="input-affix">pallets</span>
        </div>
        <p class="form-error" id="av-qty-err">
            <i class="ri-error-warning-line"></i>
            <span>At least one pallet, or there is nothing to dispatch.</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--brand btn--solid" type="button">Save connection</button>
    </div>
</form>