Skip to content

Input

Input is one styled native control. It forwards native attributes, supports framework-native value binding, exposes its element for explicit focus recovery, and merges caller Tailwind classes last.

Klean deliberately does not supply Field, Label, description, or error components. The browser's form model is the convention, so the application writes the real <label>, messages, IDs, and ARIA relationships where they remain visible.

Input.vue
Submit once to reveal the application-owned error.

Installation

One command installs one framework-native source file. There is no initializer, configuration file, alias prompt, context provider, or Klean runtime.

Run one command from a Boring Stack application. Klean detects the framework and conventional destination, then adds the framework-native source and its direct dependencies.

Terminal
npx klean-ui add input

  • No initializer or configuration file
  • No framework, alias, or theme questions
  • No Klean runtime dependency

Native form recipe

usage.vue
<script setup>
import Input from '@/components/ui/input/Input.vue'
</script>

<template>
  <div class="grid gap-2">
    <label for="email">Email address</label>
    <Input
      id="email"
      v-model="form.email"
      name="email"
      type="email"
      autocomplete="email"
      required
      :aria-invalid="Boolean(form.errors.email)"
      aria-describedby="email-help email-error"
    />
    <p id="email-help">We only use this for account messages.</p>
    <p id="email-error" class="empty:hidden text-sm text-red-700">
      {{ form.errors.email }}
    </p>
  </div>
</template>

The application owns the visible label, deterministic IDs, help and error elements, validation timing, and submitted value. Help and error nodes keep stable IDs, so aria-describedby never needs conditional string building. aria-invalid="false" is valid, and empty:hidden collapses an empty error. When an error appears, the existing relationship becomes useful automatically.

This explicit repetition is smaller and clearer than a Field configuration language or accessibility helper. Extract an application-owned form composition only when your product repeats the same complete markup and policy.

API

Input accepts native input attributes, framework-native value binding, and caller classes. It has no variant, size, tone, label, description, error, or validation props.

The default type is text. Native name, required, disabled, autocomplete, aria-invalid, and aria-describedby pass through unchanged.

Styling

The neutral defaults are monochrome, touch-safe, dark-mode aware, and visibly focusable. The 16px text default avoids mobile browser zoom. Caller Tailwind wins:

vue
<Input class="min-h-9 rounded-none border-2 py-1 text-sm shadow-none" />

If that dense treatment is a recurring product concept, create an application-owned DenseInput.vue; do not turn it into a Klean size prop.

Accessibility contract

  • Every input needs a visible associated label unless the application has a justified accessible-name alternative.
  • Help and error text connect through aria-describedby.
  • Invalid state uses aria-invalid; color is never the only signal.
  • Stable empty errors remain unannounced until they contain useful text.
  • Native required and disabled behavior stays native.
  • Focus remains visible in light, dark, and high-contrast contexts.
  • Validation waits for blur or submission instead of punishing untouched input.
  • A failed submission that needs announcement uses one application-owned error summary and focus recovery, not role="alert" on every inline error.
  • Textarea — growing multi-line input.
  • Select — one persistent value from a known fixed list.
  • Date Picker — one date-only YYYY-MM-DD value with Calendar.
  • Schedule Picker — date, time, and IANA timezone stored as an exact ISO instant.
  • Button — native form submission and actions.

All open source projects are released under the MIT License.