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.
DR.Simple_UI
Showing main. This can be ahead of the version your app has installed — check what a class says it needs before copying it. Releases (opens in a new tab)

Forms

tier 2 — classes

.form-field stacks a label, a control and a hint with the right gaps. .form-input works on input, select and textarea alike — it is width-100%, so the parent controls the size. Every control here is a native element with a class on it: no wrappers, no @bind to work around, and required / aria-invalid keep doing what they already do.

Fields

Label, control, hint. The hint explains the consequence, not the control.

How often the queue refreshes. Shorter means more database load.

<div style="max-width:420px; display:flex; flex-direction:column; gap:16px">
    <div class="form-field">
        <label class="form-label" for="f-name">Display name</label>
        <input class="form-input" id="f-name" type="text" value="Alex Fischer" />
    </div>
    <div class="form-field">
        <label class="form-label" for="f-poll">Poll interval</label>
        <select class="form-input form-select" id="f-poll">
            <option>10 seconds</option>
            <option selected>30 seconds</option>
            <option>2 minutes</option>
        </select>
        <p class="form-hint">How often the queue refreshes. Shorter means more database load.</p>
    </div>
    <div class="form-field">
        <label class="form-label" for="f-reason">Reason</label>
        <textarea class="form-input" id="f-reason" rows="3" placeholder="Why are you overriding the reservation?"></textarea>
    </div>
</div>

Selects

Add .form-select beside .form-input to replace the platform caret with one that follows the theme. The caret is drawn from two gradients — this stylesheet loads and inlines nothing, and a pseudo-element does not render on a <select> in any browser. A list box (multiple, or size above 1) drops the caret on its own, since there is nothing to drop.

<div class="field-grid" style="max-width:620px">
    <div class="form-field">
        <label class="form-label" for="f-env">Target environment</label>
        <select class="form-input form-select" id="f-env">
            <option>Production</option>
            <option selected>Staging</option>
            <option>Development</option>
        </select>
    </div>
    <div class="form-field">
        <label class="form-label" for="f-groups">Notify groups</label>
        <select class="form-input form-select" id="f-groups" multiple size="3">
            <option selected>Fulfilment</option>
            <option>Network</option>
            <option selected>On call</option>
        </select>
    </div>
</div>

Two-up rows

.field-grid lays fields out side by side and collapses to one column when there is not room for two — no breakpoint to pick.

<div class="field-grid" style="max-width:620px">
    <div class="form-field">
        <label class="form-label" for="f-from">Active from</label>
        <input class="form-input" id="f-from" type="time" value="18:00" />
    </div>
    <div class="form-field">
        <label class="form-label" for="f-to">Active to</label>
        <input class="form-input" id="f-to" type="time" value="06:00" />
    </div>
</div>

Checkboxes

.form-check is the clickable label wrapper — the whole label toggles the box. The check mark is drawn in CSS, so it picks up the brand token.

<div style="display:flex; flex-direction:column; gap:10px">
    <label class="form-check"><input type="checkbox" checked /> Desktop notifications</label>
    <label class="form-check"><input type="checkbox" /> Play a sound on new items</label>
    <label class="form-check"><input type="checkbox" checked /> Compact table density</label>
    <label class="form-check"><input type="checkbox" disabled /> Auto-advance (unavailable in demo)</label>
</div>

Radio buttons

The same .form-check wrapper — the type attribute decides the shape. A radio is an unfilled circle with a brand dot rather than a filled disc, so shape and fill differ from a checkbox and which one is exclusive reads without colour. Give every radio in a set the same name, and wrap the set in a <fieldset> so its label is announced with each option.

On failure
<fieldset class="form-section" style="max-width:420px">
    <legend>On failure</legend>
    <div class="form-section-body">
        <label class="form-check"><input type="radio" name="onfail" checked /> Retry three times, then alert</label>
        <label class="form-check"><input type="radio" name="onfail" /> Alert immediately</label>
        <label class="form-check"><input type="radio" name="onfail" /> Log only</label>
    </div>
</fieldset>

Switches

.switch is for a setting that takes effect the moment it is flipped. A checkbox is for a value saved with the rest of the form. The difference is when it applies, not how it looks — picking the wrong one misleads the reader about whether Save is still needed. It is still an <input type="checkbox">, so it needs no ARIA.

<div style="display:flex; flex-direction:column; gap:14px; max-width:460px">
    <label class="switch">
        <input type="checkbox" checked />
        <span class="switch-text">
            <span>Auto-refresh the queue</span>
            <span class="switch-hint">Applies at once. Turn off while comparing two snapshots.</span>
        </span>
    </label>
    <label class="switch"><input type="checkbox" /> Play a sound on new items</label>
    <label class="switch"><input type="checkbox" checked disabled /> Managed by policy</label>
</div>

Three sizes

.form-input-sm, the default, and .form-input-lg, matching .btn-sm, .btn and .btn-lg exactly. An input group takes the tier on the group with .input-group--sm or --lg, because the group is what draws the border.

<div class="dr-col dr-gap-2" style="max-width:420px">
    <div class="dr-row">
        <input class="form-input form-input-sm" type="search" placeholder="Small" aria-label="Small field" />
        <button class="btn btn-sm">Small</button>
    </div>
    <div class="dr-row">
        <input class="form-input" type="search" placeholder="Default" aria-label="Default field" />
        <button class="btn">Default</button>
    </div>
    <div class="dr-row">
        <input class="form-input form-input-lg" type="search" placeholder="Large" aria-label="Large field" />
        <button class="btn btn-lg">Large</button>
    </div>
    <span class="input-group input-group--sm">
        <span class="input-affix"><i class="ri-search-line"></i></span>
        <input class="form-input" type="search" placeholder="Small group" aria-label="Small group" />
        <button class="btn btn-icon" type="button" aria-label="Clear"><i class="ri-close-line"></i></button>
    </span>
    <span class="input-group input-group--lg">
        <span class="input-affix"><i class="ri-search-line"></i></span>
        <input class="form-input" type="search" placeholder="Large group" aria-label="Large group" />
        <button class="btn btn-icon" type="button" aria-label="Clear"><i class="ri-close-line"></i></button>
    </span>
</div>

Validation

The error border keys off aria-invalid, not a class. The attribute is what a screen reader announces, so styling a separate class would let the two disagree — and the reader who most needs the message is the one who would not get it. Point aria-describedby at the message so it is read as part of the field. .form-warning is for a value that is accepted but unwise.

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>

Input groups

One border around a control and its affixes. The affixes are real siblings rather than padding plus a background image, so a button stays clickable and a unit stays selectable. The focus ring moves out to the group and the inner control gives up its own, or the group draws two rings nested inside each other.

seconds
<div style="max-width:460px; display:flex; flex-direction:column; gap:16px">
    <div class="form-field">
        <label class="form-label" for="f-search">Search</label>
        <div class="input-group">
            <span class="input-affix"><i class="ri-search-line"></i></span>
            <input class="form-input" id="f-search" type="search" placeholder="Host or order number" />
        </div>
    </div>
    <div class="form-field">
        <label class="form-label" for="f-timeout">Timeout</label>
        <div class="input-group">
            <input class="form-input" id="f-timeout" type="number" value="30" />
            <span class="input-affix">seconds</span>
        </div>
    </div>
    <div class="form-field">
        <label class="form-label" for="f-token">API token</label>
        <div class="input-group">
            <input class="form-input" id="f-token" type="text" value="np_live_8f2c…" readonly />
            <button class="btn btn-icon" type="button" aria-label="Copy the API token"><i class="ri-file-copy-line"></i></button>
        </div>
    </div>
</div>

Read-only values

.form-value-display is the shape of an input without being one — for values the user can read but not change. Do not use a disabled input for this; it reads as broken rather than as informational.

alex.fischer@example.com
admin
<div class="field-grid" style="max-width:620px">
    <div class="form-field">
        <label class="form-label">Signed in as</label>
        <div class="form-value-display">alex.fischer@example.com</div>
    </div>
    <div class="form-field">
        <label class="form-label">Role</label>
        <div class="form-value-display">admin</div>
    </div>
</div>

Sections and actions

.form-section is a <fieldset> with the browser's border and inset legend removed. Use the real element: the legend is then announced with every control in the group, which a heading above a <div> is not. The fields go in .form-section-body rather than in the fieldset directly, because a <legend> is not a flex item and lands somewhere different in every engine if you make the fieldset the flex container.

.form-actions closes the form. It wraps instead of overflowing, so three buttons on a phone stack rather than pushing the commit button off-screen. .form-actions--split puts a destructive action on the far side, where it cannot be hit by aiming for Save, and .form-actions--start aligns the row to the leading edge — for a form embedded in a page rather than closing a dialog, where the far corner is nowhere in particular.

Where the group genuinely is not a set of related controls — a heading over a region rather than over fields — use .form-section-title on a plain element instead of a <legend>. It is the same rule, without claiming a grouping that is not there.

Escalation

Who is paged when nobody has acknowledged in time.

minutes
<form style="max-width:520px">
    <fieldset class="form-section">
        <legend>Escalation</legend>
        <p class="form-section-hint">Who is paged when nobody has acknowledged in time.</p>
        <div class="form-section-body">
            <div class="form-field">
                <label class="form-label form-label--required" for="f-team">Team</label>
                <select class="form-input form-select" id="f-team" required>
                    <option selected>Fulfilment</option>
                    <option>Network</option>
                </select>
            </div>
            <div class="form-field">
                <label class="form-label" for="f-after">Escalate after</label>
                <div class="input-group">
                    <input class="form-input" id="f-after" type="number" value="15" />
                    <span class="input-affix">minutes</span>
                </div>
            </div>
        </div>
    </fieldset>

    <div class="form-actions form-actions--split">
        <button class="btn btn-danger" type="button"><i class="ri-delete-bin-line"></i> Delete rule</button>
        <span style="display:flex; gap:8px">
            <button class="btn" type="button">Cancel</button>
            <button class="btn btn-primary" type="submit">Save</button>
        </span>
    </div>
</form>

A settings card

Fields in a card, actions in a footer row.

Notifications

Notifications are suppressed during this window. The queue still updates.

<div class="card" style="max-width:520px">
    <div class="card-head"><strong>Notifications</strong></div>
    <div class="card-body" style="display:flex; flex-direction:column; gap:14px">
        <label class="form-check"><input type="checkbox" checked /> Notify me about new pending items</label>
        <div class="form-field">
            <label class="form-label" for="f-quiet">Quiet hours</label>
            <input class="form-input" id="f-quiet" type="text" value="22:00 – 07:00" />
            <p class="form-hint">Notifications are suppressed during this window. The queue still updates.</p>
        </div>
    </div>
    <div class="modal-footer">
        <button class="btn">Reset</button>
        <button class="btn btn-primary">Save</button>
    </div>
</div>