Mark Design

Forms

This is a vocabulary, not a component zoo. Bootstrap supplies the components; Mark supplies the voice. For fields that voice is unusually quiet — three bindings, no new classes, no parallel implementation of anything Bootstrap already does.

What Mark changes

scss/_forms.scss

Exactly three properties, and all three for the same reason: Bootstrap resolves them from a Sass variable at build time, and Mark swaps palette, theme and mode at run time by rewriting custom properties. A value compiled into the stylesheet cannot follow. That is a mechanism mismatch, not a disagreement about taste — which is why it is the entire list.

StateBootstrapMarkWhy it had to move
Resting border $input-border-color, compiled to #dfe3ea var(--bs-border-color) Bootstrap ships no dark-mode override for it, so a field kept a light hairline on a dark page.
Focus ring rgba($primary, .15), baked at build time var(--ring) The ring stayed the build-time brand while every other focus surface followed the live palette.
Checked fill $primary, baked at build time var(--mk-primary) Same staleness: a checked box did not move when the palette did.

Everything else on this page — padding, radius, the check glyph, floating labels, validation copy, the disabled treatment — is Bootstrap's, tuned once as Sass variables where the engine can still reason about it. Before adding a rule to the form layer, check whether Bootstrap already emits it.


The field row

label / control / hint / feedback

The rhythm every editor screen repeats: a label, the control, an optional hint, and the feedback line that replaces the hint when validation speaks. Reserving the hint line keeps the form from reflowing as fields validate.

Shown in the browser tab and in search results.

Text inputs

.form-control

Sizes track the button sizes: .form-control-sm, the bare control, .form-control-lg. Focus any of them to see the ring and the 45%-alpha primary border — both follow the live palette.


Selects

.form-select

Two truths, stated honestly. .form-select is the plain case, and the browser owns the popup — no design system can style the open list on every platform, so Mark does not pretend to. When the menu itself has to carry the voice, that is the listbox pattern, and it lives under UI rather than here, because it is built from a button and a dropdown rather than from a field.


Checks, radios, switches

.form-check

One family, three appearances. The checked fill is var(--mk-primary), so all three move together when the palette does — that is the one binding worth seeing on this page.


Validation

.is-valid / .is-invalid

Bootstrap's, unmodified. The state colours are --bs-danger and --bs-success, which deliberately do not re-tint per palette: an error should read as an error in every theme rather than arriving in the brand colour.

Enter an address that includes an @.
Available.

What Bootstrap does not ship

the UI boundary

The controls above are the ones Bootstrap has. The ones it does not — a multi-select, a token or chip input, a combobox that filters as you type — are common enough elsewhere that their absence is felt, and they are the honest argument for a section beyond this one.

They are filed under UI rather than Forms, because they are built from a button and a dropdown rather than from a field, and because a chip is not form-specific: filters, facets and tags on a card all want the same object. Building them as compositions of Bootstrap's dropdown — whose popper, keyboard handling and dismissal are never reimplemented — is what keeps this a vocabulary rather than a zoo.