ComponentsThemes
Palette
Default
Stone
Rose
Blue
Green
Violet
Yellow
Orange
6

← Components

Input

A styled text input with focus ring and disabled state.

Installation

bin/rails g wabi:add input

Example

Invalid state

Pass invalid: true to render aria-invalid="true", which assistive tech announces and styles can target via aria-[invalid]:.

No separate component needed — a search field is Input type="search" with a leading icon. type="search" also gives you the browser's native clear (×) button while typing — no JavaScript.

Need search-as-you-type with a suggestions dropdown? Use the async Combobox instead — it wires the debounced fetch and result list for you.

Source

app/components/ui/input.rb

# frozen_string_literal: true

module Components
  module UI
    # Accessibility: an <input> needs an accessible name. This primitive forwards
    # all attrs, so callers MUST supply one of: an associated <label for=> (pass a
    # matching `id:`), a wrapping <label>, or `aria_label:` / `aria-label:` directly.
    # A bare Input with none of these is an unlabelled control (WCAG 4.1.2 fail).
    class Input < Wabi::Base
      variants do
        base "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 " \
             "text-sm ring-offset-background file:border-0 file:bg-transparent " \
             "file:text-sm file:font-medium placeholder:text-muted-foreground " \
             "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring " \
             "focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 " \
             "aria-[invalid=true]:border-destructive aria-[invalid=true]:focus-visible:ring-destructive"
      end

      def initialize(type: "text", invalid: false, **attrs)
        @type    = type
        @invalid = invalid
        @attrs   = attrs
      end

      def view_template
        user_class = @attrs.delete(:class)
        input(
          type: @type,
          aria_invalid: (@invalid ? "true" : nil),
          **@attrs,
          class: merge_class(tokens, user_class)
        )
      end
    end
  end
end

Accessibility

  • native <input> element — full browser keyboard support out of the box.
  • focus-visible:ring keeps focus state visible without click pollution.
  • disabled state via disabled:opacity-50 + disabled:cursor-not-allowed.
  • inherits type-specific affordances (email keyboard on mobile, etc.).