Skip to content
WovenUI LogoWovenUI

Blocks

Form field pattern

Three labeled form fields: an email input with a hint, a select, and an input showing an error message.

Preview

We send the sign-in link to this address.

Enter the name of your company.

Live preview of the form field, rendered from the HTML on this page.

HTML to copy

The markup of the preview, in WovenUI core utility classes. It needs only the prebuilt@wovenui/core/dist/wovenui.min.css, or your own build with $generate-utility-classes: true. No components package and no JavaScript.

HTML
<div class="flex col gap-6 max-w-sm">
    <div class="flex col gap-1">
        <label class="text-sm font-medium" for="signup-email">Work email</label>
        <input id="signup-email" name="email" type="email" autocomplete="email" aria-describedby="signup-email-hint" required>
        <p class="text-sm text-muted m-0" id="signup-email-hint">We send the sign-in link to this address.</p>
    </div>
    <div class="flex col gap-1">
        <label class="text-sm font-medium" for="signup-size">Team size</label>
        <select id="signup-size" name="size">
            <option>1 to 10 people</option>
            <option>11 to 50 people</option>
            <option>More than 50 people</option>
        </select>
    </div>
    <div class="flex col gap-1">
        <label class="text-sm font-medium" for="signup-company">Company</label>
        <input id="signup-company" name="company" type="text" autocomplete="organization" aria-invalid="true" aria-describedby="signup-company-error">
        <p class="text-sm text-danger m-0" id="signup-company-error">Enter the name of your company.</p>
    </div>
</div>

With apply()

The same result with BEM class names and no utility classes. @use "@wovenui/core" as *; emits the tokens and base styles, and each apply() writes one rule.

SCSS
@use "@wovenui/core" as *;

// Inputs and selects take their border, focus ring and invalid state from the base layer
.fields {
    @include apply(
        flex, // display: flex
        col, // flex direction: column
        gap(6), // gap: spacing key 6 (1.5rem)
        max-width(sm) // max width: the sm breakpoint, 640px
    );
}

.field {
    @include apply(
        flex, // display: flex
        col, // flex direction: column
        gap(1) // gap: spacing key 1 (0.25rem)
    );
}

.field__label {
    @include apply(
        font-size(sm), // font size key sm (0.875rem)
        font(medium) // font weight: medium (500)
    );
}

.field__hint {
    @include apply(
        m(0), // margin: 0
        font-size(sm), // font size key sm (0.875rem)
        text-color(muted) // muted text color, follows the theme
    );
}

.field__error {
    @include apply(
        m(0), // margin: 0
        font-size(sm), // font size key sm (0.875rem)
        text-color(danger) // danger (error) text color
    );
}
HTML
<div class="fields">
    <div class="field">
        <label class="field__label" for="signup-email">Work email</label>
        <input id="signup-email" name="email" type="email" autocomplete="email" aria-describedby="signup-email-hint" required>
        <p class="field__hint" id="signup-email-hint">We send the sign-in link to this address.</p>
    </div>
    <div class="field">
        <label class="field__label" for="signup-size">Team size</label>
        <select id="signup-size" name="size">
            <option>1 to 10 people</option>
            <option>11 to 50 people</option>
            <option>More than 50 people</option>
        </select>
    </div>
    <div class="field">
        <label class="field__label" for="signup-company">Company</label>
        <input id="signup-company" name="company" type="text" autocomplete="organization" aria-invalid="true" aria-describedby="signup-company-error">
        <p class="field__error" id="signup-company-error">Enter the name of your company.</p>
    </div>
</div>

Tokens used

Every color comes from a theme token, so the pattern follows the light, dark and inverse themes with no extra CSS.

bg-surface, text-regular
Field background and typed text, from the base layer.
border-control, border-control-hover
Field edge at rest and on hover, at 3:1 contrast against the page.
border-focus, border-focus-width, border-focus-offset
The focus ring the base layer draws on :focus-visible.
danger, text-danger
The border of a field with aria-invalid="true" or :user-invalid, and the error message.
text-muted, text-subtle
The hint, and placeholder text.