Form controls

Inputs, selects, textareas, checkboxes, radios, and labels.

.v-input, .v-select, .v-textarea, .v-checkbox, and .v-radio style their respective native elements. .v-form-label styles labels; data-size scales size; aria-invalid marks errors.

.v-input / .v-textarea / .v-select

data-size scales height, padding, font size, and border radius proportionally. Medium is the default — omit the attribute for the standard size.

Attribute Values
data-size "small", "medium", "large"

Text-like inputs on <input> elements:

Show editor
<div class="stack-v gap-2xs">
  <label class="v-form-label" for="name">Full name</label>
  <input
    class="v-input"
    type="text"
    id="name"
    placeholder="e.g. Ola Nordmann"
  />
</div>

Textareas on <textarea>:

Show editor
<div class="stack-v gap-2xs">
  <label class="v-form-label" for="message">Message</label>
  <textarea
    class="v-textarea"
    id="message"
    rows="3"
    placeholder="Write something…"
  ></textarea>
</div>

Selects on <select>. A custom chevron is rendered via background-image — no wrapper element or icon needed. Supports <optgroup> labels, <hr> separators, and disabled <option> elements.

Show editor
<div class="stack-v gap-2xs">
  <label class="v-form-label" for="country">Country</label>
  <select class="v-select" id="country">
    <option value="">Choose…</option>
    <option value="no">Norway</option>
    <option value="se">Sweden</option>
    <option value="dk" disabled>Denmark</option>
  </select>
</div>

Sizes

All three control types share the same sizing scale:

Show editor
<div class="stack-h gap-m">
  <div class="stack-v gap-s w-full flex-1">
    <input class="v-input" type="text" data-size="small" placeholder="Small" />
    <input class="v-input" type="text" placeholder="Medium" />
    <input class="v-input" type="text" data-size="large" placeholder="Large" />
  </div>

  <div class="stack-v gap-s w-full flex-1">
    <select class="v-select" data-size="small">
      <option>Small</option>
      <option>Small</option>
      <option>Small</option>
    </select>
    <select class="v-select">
      <option>Medium</option>
      <option>Medium</option>
      <option>Medium</option>
    </select>
    <select class="v-select" data-size="large">
      <option>Large</option>
      <option>Large</option>
      <option>Large</option>
    </select>
  </div>

  <div class="stack-v gap-s w-full flex-1">
    <textarea
      class="v-textarea"
      data-size="small"
      rows="1"
      placeholder="Small"
    ></textarea>
    <textarea class="v-textarea" rows="1" placeholder="Medium"></textarea>
    <textarea
      class="v-textarea"
      data-size="large"
      rows="1"
      placeholder="Large"
    ></textarea>
  </div>
</div>

Inputs and buttons at matching sizes align naturally in a row:

Show editor
<div class="stack-v gap-xs">
  <div class="stack-h gap-s">
    <input
      class="v-input grow"
      type="text"
      data-size="small"
      placeholder="Search…"
    />
    <button class="v-button " data-size="small">Go</button>
  </div>

  <div class="stack-h gap-s">
    <input
      class="v-input grow"
      type="text"
      data-size="medium"
      placeholder="Search…"
    />
    <button class="v-button " data-size="medium">Go</button>
  </div>

  <div class="stack-h gap-s">
    <input
      class="v-input grow"
      type="text"
      data-size="large"
      placeholder="Search…"
    />
    <button class="v-button " data-size="large">Go</button>
  </div>
</div>

Advanced select

With optgroups and separators. In Chromium-based browsers, the dropdown is fully styled (optgroup labels, checkmarks, hover states). Other browsers get a custom chevron with a native dropdown.

Show editor
<select class="v-select">
  <option value="">Choose a food</option>
  <hr />
  <optgroup label="Fruit">
    <legend>Fruit</legend>
    <option value="apple">Apple</option>
    <option value="banana">Banana</option>
  </optgroup>
  <hr />
  <optgroup label="Vegetables">
    <legend>Vegetables</legend>
    <option value="artichoke">Artichoke</option>
    <option value="broccoli">Broccoli</option>
  </optgroup>
</select>

Range input

Show editor
<input class="v-range" type="range" min="0" max="100" step="5" />

.v-radio / .v-checkbox

.v-checkbox and .v-radio replace the native appearance with a styled check or dot mark. The checked state fills the mark automatically — no extra classes needed. A paired <label> is required.

Attribute Values
data-size "small", "medium", "large"
Show editor
<div class="stack-h gap-s">
  <input class="v-checkbox" type="checkbox" id="terms" checked />
  <label for="terms">I accept the terms</label>
</div>
Show editor
<div class="stack-h gap-s">
  <input class="v-radio" type="radio" name="choice" id="opt-a" checked />
  <label for="opt-a">Option A</label>
</div>
<div class="stack-h gap-s">
  <input class="v-radio" type="radio" name="choice" id="opt-b" />
  <label for="opt-b">Option B</label>
</div>

Sizes

Both controls share the same sizing scale:

Show editor
<div class="stack-v gap-xs">
  <div class="stack-h items-center gap-m">
    <div class="stack-h gap-2xs">
      <input class="v-checkbox" type="checkbox" data-size="small" checked />
      <label>Small</label>
    </div>
    <div class="stack-h gap-2xs">
      <input class="v-checkbox" type="checkbox" checked />
      <label>Medium</label>
    </div>
    <div class="stack-h gap-2xs">
      <input class="v-checkbox" type="checkbox" data-size="large" checked />
      <label>Large</label>
    </div>
  </div>
  <div class="stack-h items-center gap-m">
    <div class="stack-h gap-2xs">
      <input
        class="v-radio"
        type="radio"
        name="size"
        data-size="small"
        checked
      />
      <label>Small</label>
    </div>
    <div class="stack-h gap-2xs">
      <input class="v-radio" type="radio" name="size" />
      <label>Medium</label>
    </div>
    <div class="stack-h gap-2xs">
      <input class="v-radio" type="radio" name="size" data-size="large" />
      <label>Large</label>
    </div>
  </div>
</div>

Labels

.v-form-label provides consistent label styling: muted color, small size, medium weight.

Show editor
<div class="stack-v gap-2xs">
  <label class="v-form-label" for="email">Email</label>
  <input class="v-input" type="email" id="email" />
</div>

Error state

aria-invalid="true" turns the border and text red automatically — no custom error class needed. The error message color is set by a <color-mode palette="coral"> wrapper:

Please enter a valid email address

Show editor
<div class="stack-v">
  <label class="v-form-label" for="email">Email</label>
  <input
    class="v-input"
    type="email"
    id="email"
    value="not-an-email"
    aria-invalid="true"
  />
  <color-mode palette="coral">
    <p class="fs-xs fw-medium mt-2xs">Please enter a valid email address</p>
  </color-mode>
</div>

Disabled state

Disabled controls get cursor: not-allowed and no visual greying out, consistent with the button component. Unavailability is communicated with an explanation rather than a dimmed appearance.

Putting it together

A realistic form combining labels, layout, validation, and multiple control types:

Please enter a valid email address

Show editor
<form class="stack-v gap-m">
  <div class="stack-h items-start gap-m">
    <div class="stack-v gap-2xs grow">
      <label class="v-form-label" for="first">First name</label>
      <input class="v-input" type="text" id="first" placeholder="Ola" />
    </div>
    <div class="stack-v gap-2xs grow">
      <label class="v-form-label" for="last">Last name</label>
      <input class="v-input" type="text" id="last" placeholder="Nordmann" />
    </div>
  </div>

  <div class="stack-v">
    <label class="v-form-label mb-3xs" for="email">Email</label>
    <input
      class="v-input"
      type="email"
      id="email"
      value="bad"
      aria-invalid="true"
    />
    <color-mode palette="coral">
      <p class="fs-xs fw-medium mt-2xs">Please enter a valid email address</p>
    </color-mode>
  </div>

  <div class="stack-v gap-2xs">
    <label class="v-form-label" for="country">Country</label>
    <select class="v-select" id="country">
      <option value="">Choose…</option>
      <option value="no">Norway</option>
      <option value="se">Sweden</option>
      <option value="dk">Denmark</option>
    </select>
  </div>

  <div class="stack-v gap-2xs">
    <label class="v-form-label" for="message">Message</label>
    <textarea
      class="v-textarea"
      id="message"
      rows="3"
      placeholder="Write something…"
    ></textarea>
  </div>

  <div class="stack-h gap-s">
    <input class="v-checkbox" type="checkbox" id="terms" />
    <label class="fs-s" for="terms">I accept the terms and conditions</label>
  </div>

  <button class="v-button ml-auto px-xl" type="submit">Submit</button>
</form>