Skip to main content
Full documentation available at velinstyle.info — This is the quick reference. Visit the full docs for 80+ pages with live examples, API tables, and guides.

Forms

Accessible, consistent form controls built with modern CSS. Every control meets WCAG 2.2 touch targets (44×44px minimum) and works across all themes.

Text Input

Use .velin-input on any <input> element. Supports all HTML5 types including text, email, password, number, search, and url.

<input type="text" class="velin-input" placeholder="Default text input">
<input type="email" class="velin-input" placeholder="Email input">
<input type="password" class="velin-input" placeholder="Password input">
<input type="text" class="velin-input" placeholder="Disabled input" disabled>

Select

Apply .velin-select for a styled native dropdown with a custom chevron indicator.

<select class="velin-select">
  <option value="" disabled selected>Choose an option&hellip;</option>
  <option value="1">Option One</option>
  <option value="2">Option Two</option>
  <option value="3">Option Three</option>
</select>

<select class="velin-select" disabled>
  <option>Disabled select</option>
</select>

Textarea

Use .velin-textarea for multi-line text fields. The textarea is vertically resizable by default with a minimum height of 6rem.

<textarea class="velin-textarea" placeholder="Write something…"></textarea>

<textarea class="velin-textarea" placeholder="Disabled textarea" disabled></textarea>

Checkbox

Wrap a checkbox in .velin-check with __input and __label elements. The label is the clickable wrapper via the <label> element.

<label class="velin-check">
  <input type="checkbox" class="velin-check__input">
  <span class="velin-check__label">Accept terms and conditions</span>
</label>

<label class="velin-check">
  <input type="checkbox" class="velin-check__input" checked>
  <span class="velin-check__label">Checked by default</span>
</label>

<label class="velin-check">
  <input type="checkbox" class="velin-check__input" disabled>
  <span class="velin-check__label">Disabled checkbox</span>
</label>

Radio

Radio groups use .velin-radio with the same BEM structure as checkboxes. Group them with a shared name attribute.

Select a plan
<fieldset style="border:none;padding:0;margin:0">
  <legend class="velin-label">Select a plan</legend>

  <label class="velin-radio">
    <input type="radio" name="plan" class="velin-radio__input" value="free" checked>
    <span class="velin-radio__label">Free</span>
  </label>

  <label class="velin-radio">
    <input type="radio" name="plan" class="velin-radio__input" value="pro">
    <span class="velin-radio__label">Pro</span>
  </label>

  <label class="velin-radio">
    <input type="radio" name="plan" class="velin-radio__input" value="enterprise" disabled>
    <span class="velin-radio__label">Enterprise (disabled)</span>
  </label>
</fieldset>

Switch / Toggle

The .velin-switch uses a hidden checkbox with a custom track and thumb. Available in three sizes: --sm, default, and --lg.

Sizes

<!-- Default switch -->
<label class="velin-switch">
  <input type="checkbox" class="velin-switch__input" role="switch">
  <span class="velin-switch__track"></span>
  <span class="velin-switch__label">Notifications</span>
</label>

<!-- Checked -->
<label class="velin-switch">
  <input type="checkbox" class="velin-switch__input" role="switch" checked>
  <span class="velin-switch__track"></span>
  <span class="velin-switch__label">Dark mode (on)</span>
</label>

<!-- Disabled -->
<label class="velin-switch">
  <input type="checkbox" class="velin-switch__input" role="switch" disabled>
  <span class="velin-switch__track"></span>
  <span class="velin-switch__label">Disabled</span>
</label>

<!-- Sizes -->
<label class="velin-switch velin-switch--sm">…</label>
<label class="velin-switch">…</label>
<label class="velin-switch velin-switch--lg">…</label>

Range

Apply .velin-range to a range input for a custom-styled slider with accessible focus indicators.

<div class="velin-field">
  <label class="velin-label" for="volume">Volume</label>
  <input type="range" id="volume" class="velin-range" min="0" max="100" value="60">
</div>

<!-- Disabled -->
<input type="range" class="velin-range" min="0" max="100" value="40" disabled>

File Upload

The .velin-file creates a drag-and-drop styled upload zone. Use __input, __icon, __label, and __hint for the inner elements. A compact variant is also available.

Drop files here or click to upload PNG, JPG, or PDF up to 10MB

Compact variant

Choose file Max 10MB
<!-- Full dropzone -->
<div class="velin-file">
  <input type="file" class="velin-file__input" aria-label="Upload file">
  <svg class="velin-file__icon" width="32" height="32" viewBox="0 0 24 24"
    fill="none" stroke="currentColor" stroke-width="1.5">
    <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
    <polyline points="17 8 12 3 7 8"/>
    <line x1="12" y1="3" x2="12" y2="15"/>
  </svg>
  <span class="velin-file__label">Drop files here or click to upload</span>
  <span class="velin-file__hint">PNG, JPG, or PDF up to 10MB</span>
</div>

<!-- Compact variant -->
<div class="velin-file velin-file--compact">
  <input type="file" class="velin-file__input" aria-label="Upload file">
  <svg class="velin-file__icon" …>…</svg>
  <span class="velin-file__label">Choose file</span>
  <span class="velin-file__hint">Max 10MB</span>
</div>

Labels

Use .velin-label on <label> elements. Add .velin-label--required to automatically append a red asterisk.

<label class="velin-label" for="username">Username</label>
<input type="text" id="username" class="velin-input">

<label class="velin-label velin-label--required" for="email">Email</label>
<input type="email" id="email" class="velin-input" required>

Help Text

Add .velin-field-help below an input to provide contextual guidance. Link it with aria-describedby for accessibility.

Must be at least 8 characters with one number.

<div class="velin-field">
  <label class="velin-label" for="password">Password</label>
  <input type="password" id="password" class="velin-input"
    aria-describedby="password-hint">
  <p class="velin-field-help" id="password-hint">
    Must be at least 8 characters with one number.
  </p>
</div>

Error States

Add .velin-input--error (or .velin-select--error, .velin-textarea--error) to mark invalid fields. Pair with .velin-field-error for the error message and aria-invalid="true" for screen readers.

Please enter a valid email address.

Please select a country.

Bio is required.

Success state

<!-- Error state -->
<div class="velin-field">
  <label class="velin-label velin-label--required" for="email">Email</label>
  <input type="email" id="email"
    class="velin-input velin-input--error"
    aria-invalid="true"
    aria-describedby="email-error">
  <p class="velin-field-error" id="email-error">
    Please enter a valid email address.
  </p>
</div>

<!-- Success state -->
<input class="velin-input velin-input--success" value="johndoe">

<!-- Also available for select & textarea -->
<select class="velin-select velin-select--error">…</select>
<textarea class="velin-textarea velin-textarea--error">…</textarea>

Validation CSS (:user-invalid)

Add .velin-form--validated on the <form>. Browsers that support :user-invalid only show error borders after the user has interacted, avoiding an all-red form on first paint. Pair with aria-invalid and aria-describedby for screen readers.

Use .velin-field-hint (alias of help styling) or .velin-field-help, .velin-field-error, and optionally .velin-field-error--icon for a leading accent. Dynamic messages: role="status" + aria-live="polite" or role="alert" for urgent errors.

We never share your email.

<form class="velin-form--validated" novalidate>
  <input class="velin-input" required aria-describedby="hint-id">
  <p class="velin-field-hint" id="hint-id">…</p>
  <div role="status" aria-live="polite" id="live"></div>
</form>

Field Wrapper

Use .velin-field to group a label, input, and help/error text. It provides consistent vertical spacing between form rows.

As it appears on your ID.

This email is already in use.

Brief description (max 200 characters).

<div class="velin-field">
  <label class="velin-label velin-label--required" for="name">Full name</label>
  <input type="text" id="name" class="velin-input" required>
  <p class="velin-field-help">As it appears on your ID.</p>
</div>

<div class="velin-field">
  <label class="velin-label velin-label--required" for="email">Email</label>
  <input type="email" id="email"
    class="velin-input velin-input--error"
    aria-invalid="true"
    aria-describedby="email-err"
    required>
  <p class="velin-field-error" id="email-err">
    This email is already in use.
  </p>
</div>

Complete Contact Form

A full example combining all form controls with proper labels, accessibility attributes, and validation styling.

First and last name.

Please enter a valid email address.

Minimum 20 characters.

Approximate budget in USD.

Attach a file Max 10MB
Preferred contact method
<form novalidate>
  <div class="velin-field">
    <label class="velin-label velin-label--required" for="name">Full Name</label>
    <input type="text" id="name" class="velin-input" required
      aria-describedby="name-help">
    <p class="velin-field-help" id="name-help">First and last name.</p>
  </div>

  <div class="velin-field">
    <label class="velin-label velin-label--required" for="email">Email</label>
    <input type="email" id="email"
      class="velin-input velin-input--error"
      aria-invalid="true"
      aria-describedby="email-err" required>
    <p class="velin-field-error" id="email-err">
      Please enter a valid email address.
    </p>
  </div>

  <div class="velin-field">
    <label class="velin-label" for="phone">Phone</label>
    <input type="tel" id="phone" class="velin-input">
  </div>

  <div class="velin-field">
    <label class="velin-label velin-label--required" for="subject">Subject</label>
    <select id="subject" class="velin-select" required>
      <option value="" disabled selected>Choose a topic…</option>
      <option value="general">General Inquiry</option>
      <option value="support">Technical Support</option>
    </select>
  </div>

  <div class="velin-field">
    <label class="velin-label velin-label--required" for="message">Message</label>
    <textarea id="message" class="velin-textarea" required
      aria-describedby="msg-help"></textarea>
    <p class="velin-field-help" id="msg-help">Minimum 20 characters.</p>
  </div>

  <div class="velin-field">
    <label class="velin-label" for="budget">Budget Range</label>
    <input type="range" id="budget" class="velin-range"
      min="0" max="10000" step="500" value="2500">
  </div>

  <div class="velin-field">
    <label class="velin-label">Attachment</label>
    <div class="velin-file velin-file--compact">
      <input type="file" class="velin-file__input" aria-label="Upload">
      <svg class="velin-file__icon" …>…</svg>
      <span class="velin-file__label">Attach a file</span>
      <span class="velin-file__hint">Max 10MB</span>
    </div>
  </div>

  <fieldset class="velin-field">
    <legend class="velin-label">Preferred contact method</legend>
    <label class="velin-radio">
      <input type="radio" name="contact" class="velin-radio__input"
        value="email" checked>
      <span class="velin-radio__label">Email</span>
    </label>
    <label class="velin-radio">
      <input type="radio" name="contact" class="velin-radio__input"
        value="phone">
      <span class="velin-radio__label">Phone</span>
    </label>
  </fieldset>

  <div class="velin-field">
    <label class="velin-switch">
      <input type="checkbox" class="velin-switch__input" role="switch">
      <span class="velin-switch__track"></span>
      <span class="velin-switch__label">Subscribe to newsletter</span>
    </label>
  </div>

  <div class="velin-field">
    <label class="velin-check">
      <input type="checkbox" class="velin-check__input" required>
      <span class="velin-check__label">I agree to the terms</span>
    </label>
  </div>

  <button type="submit" class="velin-btn velin-btn--primary">
    Send Message
  </button>
</form>

Theme waehlen