Input
Flexible text input component with multiple types, variants, and built-in validation.
Key Features
- 7 Input Types - text, email, password, number, tel, url, search
- 3 Variants - Default, Filled, Outline
- Password Toggle - Automatic show/hide for password inputs
- Prefix/Suffix Slots - Add icons or text before/after input
- Validation - Pattern, maxlength, minlength, min, max, step
- Form Builder - Full Rails form integration (
f.rui_input) - 5 Sizes - xs, sm, base, lg, xl
- 3 Shapes - Square, rounded, pill
- All Colors - Semantic + any Tailwind color for focus ring
- Full Accessibility - ARIA attributes, keyboard navigation
- JavaScript API - Programmatic control with
data-actionattributes
Basic Usage
Simple text input with label and help text.
We'll never share your email
<%= rui_input(name: :username, label: "Username") %>
<%= rui_input(
name: :email,
type: :email,
label: "Email Address",
help_text: "We'll never share your email"
) %>
Input Types
Seven input types for different data formats and validation.
<%= rui_input(name: :username, type: :text, label: "Text") %>
<%= rui_input(name: :email, type: :email, label: "Email") %>
<%= rui_input(name: :password, type: :password, label: "Password") %>
<%= rui_input(name: :quantity, type: :number, label: "Number") %>
<%= rui_input(name: :phone, type: :tel, label: "Phone") %>
<%= rui_input(name: :website, type: :url, label: "Website") %>
<%= rui_input(name: :query, type: :search, label: "Search") %>
type: :tel auto-formats as North American 10-digit (XXX) XXX-XXXX while typing, and type: :url auto-prefixes https:// on blur. The phone formatter is hardcoded to NANP, so it will strip/reflow digits into the wrong shape for any non-NANP number. Pass autoformat: false to disable both formatters and get a plain, unformatted input.
<%= f.rui_input(:phone, type: :tel, autoformat: false, label: "Phone Number") %>
Variants
Three visual variants for different design contexts.
Default
Standard bordered input with white background.
<%= rui_input(name: :username, variant: :default, label: "Username") %>
Filled
Input with filled background and transparent border.
<%= rui_input(name: :username, variant: :filled, label: "Username") %>
Outline
Transparent background with visible border.
<%= rui_input(name: :username, variant: :outline, label: "Username") %>
Sizes
Five size options from extra small to extra large.
<%= rui_input(name: :field, size: :xs, label: "Extra Small") %>
<%= rui_input(name: :field, size: :sm, label: "Small") %>
<%= rui_input(name: :field, size: :base, label: "Base (default)") %>
<%= rui_input(name: :field, size: :lg, label: "Large") %>
<%= rui_input(name: :field, size: :xl, label: "Extra Large") %>
Shapes
Three shape options for border radius.
<%= rui_input(name: :field, shape: :square, label: "Square") %>
<%= rui_input(name: :field, shape: :rounded, label: "Rounded") %>
<%= rui_input(name: :field, shape: :pill, label: "Pill") %>
Colors
Focus ring colors using semantic and Tailwind colors.
Semantic Colors
<%= rui_input(name: :field, color: :primary, label: "Primary") %>
<%= rui_input(name: :field, color: :success, label: "Success") %>
<%= rui_input(name: :field, color: :danger, label: "Danger") %>
Tailwind Colors
Use any Tailwind color for the focus ring!
Tip: Colors are generated dynamically using ColorBuilderHelper, supporting the entire Tailwind color palette.
Password Toggle
Password inputs automatically include a show/hide toggle button using Stimulus.
Click the eye icon to toggle visibility
<%= rui_input(
name: :password,
type: :password,
label: "Password",
placeholder: "Enter your password"
) %>
Automatic: The password toggle is automatically added to type: :password inputs. The eye icon swaps to eye-off when password is visible. Uses Stimulus controller for smooth interaction.
Prefix & Suffix
Add icons or text before or after the input using slots.
With Icons
<%= rui_input(name: :search, type: :search, label: "Search") do |input| %%>
<% input.with_prefix do %%>
<%= rui_icon :search, size: :sm, standalone: false %%>
<% end %%>
<% end %%>
With Text
<%= rui_input(
name: :website,
type: :text,
label: "Website",
pattern: "[a-zA-Z0-9][a-zA-Z0-9\-]*\.[a-zA-Z]{2,}",
title: "Enter a valid domain (e.g., example.com)"
) do |input| %%>
<% input.with_prefix do %%>
<%= tag.span("https://", class: "text-xs font-medium") %%>
<% end %%>
<% end %%>
<%= rui_input(name: :price, type: :number, step: "0.01", min: 0, label: "Price") do |input| %%>
<% input.with_suffix do %%>
<%= tag.span("USD", class: "text-xs font-medium") %%>
<% end %%>
<% end %%>
Note: Password inputs with type: :password use the suffix slot automatically for the toggle button. If you provide a suffix slot on a password input, the toggle button won't be added.
Validation
HTML5 validation attributes for client-side validation.
Pattern & Length
3-20 characters, alphanumeric and underscores only
5-digit US ZIP code
<%= rui_input(
name: :username,
pattern: "[a-zA-Z0-9_]+",
minlength: 3,
maxlength: 20,
label: "Username"
) %>
Number Constraints
Must be between 18 and 120
In increments of 5 (0, 5, 10... 100)
Accepts decimal values
<%= rui_input(
name: :age,
type: :number,
min: 18,
max: 120,
label: "Age"
) %>
<%= rui_input(
name: :price,
type: :number,
min: 0,
step: "0.01",
label: "Price"
) %>
States
Required, disabled, readonly, and error states.
<%= rui_input(name: :field, required: true, label: "Required") %>
<%= rui_input(name: :field, disabled: true, label: "Disabled") %>
<%= rui_input(name: :field, readonly: true, label: "Readonly") %>
Custom Styling
Override or extend styles using class override attributes. RapidRailsUI uses tailwind_merge to intelligently merge custom classes - conflicting classes are properly overridden, not just appended.
Available Class Overrides
class:- Applied to the input element itselfwrapper_class:- Applied to the outer containerlabel_class:- Applied to the label elementprefix_class:- Applied to the prefix containersuffix_class:- Applied to the suffix containercontainer_class:- Applied to the input container (when has prefix/suffix)help_text_class:- Applied to the help text
Input & Wrapper Examples
<%= rui_input(name: :field, wrapper_class: "w-64") %%>
<%= rui_input(name: :field, wrapper_class: "max-w-xs") %%>
<%= rui_input(name: :field, class: "border-purple-500 focus:ring-purple-500") %%>
<%= rui_input(
name: :field,
wrapper_class: "w-80",
class: "bg-amber-50 border-amber-300"
) %%>
Label & Help Text Examples
This help text has custom styling
<%= rui_input(
name: :field,
label: "Custom Label",
label_class: "text-indigo-600 font-bold"
) %%>
<%= rui_input(
name: :field,
help_text: "Custom styled help",
help_text_class: "text-indigo-500 font-medium"
) %%>
Prefix & Suffix Examples
<%= rui_input(name: :price, prefix_class: "bg-indigo-100 rounded-l-lg") do |input| %%>
<% input.with_prefix do %%>$<% end %%>
<% end %%>
<%= rui_input(name: :email, suffix_class: "text-emerald-500") do |input| %%>
<% input.with_suffix do %%>
<%= rui_icon :check_circle, size: :sm, standalone: false %%>
<% end %%>
<% end %%>
Combined Styling Example (All 7 Overrides)
This example demonstrates ALL 7 class override options working together.
All 7 class overrides applied
<%= rui_input(
name: :price,
label: "Fully Customized Input",
help_text: "All 7 class overrides applied",
placeholder: "0.00",
type: :number,
# 1. wrapper_class - outer container (layout, width)
wrapper_class: "max-w-md",
# 2. class - input element itself
class: "border-violet-300 focus:ring-violet-500",
# 3. label_class - label element
label_class: "text-violet-700 font-semibold",
# 4. help_text_class - help text below input
help_text_class: "text-violet-500 italic",
# 5. container_class - input container (with prefix/suffix)
container_class: "bg-violet-50 rounded-lg",
# 6. prefix_class - prefix container
prefix_class: "bg-violet-100 text-violet-600",
# 7. suffix_class - suffix container
suffix_class: "bg-violet-100 text-violet-600"
) do |input| %%>
<% input.with_prefix do %%>
<%= tag.span("$", class: "text-sm font-bold") %%>
<% end %%>
<% input.with_suffix do %%>
<%= tag.span("USD", class: "text-xs font-medium") %%>
<% end %%>
<% end %%>
Smart Merging: Thanks to tailwind_merge, your custom classes properly override defaults. For example, border-purple-500 will replace the default border-zinc-300, not just be appended.
Form Builder Integration
Use with Rails form builder for automatic object binding and validation.
<%= form_with model: @user do |f| %%>
<%= f.rui_input(:username,
label: "Username",
placeholder: "Enter username",
required: true
) %%>
<%= f.rui_input(:email,
type: :email,
label: "Email Address",
autocomplete: "email"
) %%>
<%= f.rui_input(:password,
type: :password,
label: "Password",
minlength: 8,
help_text: "At least 8 characters"
) %%>
<%= f.rui_input(:age,
type: :number,
label: "Age",
min: 18,
max: 120
) %%>
<%= f.rui_input(:website,
type: :url,
label: "Website",
placeholder: "https://example.com"
) %%>
<%= f.rui_button("Sign Up") %%>
<% end %%>
Note: When used through the form builder (f.rui_input), errors are detected automatically from @user.errors[:email] — no extra options needed. This only works via f.rui_input; standalone rui_input(object: @user, method: :email) does not check model errors.
Error Display
Errors from Rails validations are automatically displayed. Try typing an invalid email to see real-time validation.
Type an invalid email to see error, valid email to see success
Automatic error handling: When used with f.rui_input, Rails model errors render red border styling, aria-invalid="true", and the error message text — automatically. The error message renders inside the same validation-message element the input-validation Stimulus controller uses for real-time feedback, not a second element, so there's no double-rendering. On first paint (e.g. a 422 re-render after a failed create/update), the server-rendered error wins; once the user edits the field, client-side validation takes back over.
422 Re-Render Example
The most common place this shows up — a failed create re-renders the form and the input shows the server-side error with no extra code.
def create
@user = User.new(user_params)
if @user.save
redirect_to @user
else
render :new, status: :unprocessable_entity
end
end
<%= form_with model: @user do |f| %>
<%= f.rui_input(:email, type: :email, label: "Email") %>
<%# @user.errors[:email] renders inline automatically if #create failed %>
<% end %>
Real-World Example: Post Form
See the input component in action on the New Post and Posts pages.
Accessibility
The Input component follows WAI-ARIA best practices for form controls.
ARIA Attributes
-
Help text and errors linked via
aria-describedby(error id wins when both are present) — wired even on avalidation: falsefield, as long as a real server-side model error is present on it -
Required fields include
aria-required="true" -
Error states include
aria-invalid="true" -
Error messages displayed with
role="alert" -
Password toggle has
aria-label="Toggle password visibility"
Keyboard Navigation
- Tab moves focus between inputs
- Focus states are clearly visible with ring styles
- Password toggle button is keyboard accessible
Semantic HTML
-
Uses native
<input>elements with appropriate types -
Labels use
forattribute linked to inputid - Input types match content (email, tel, url, password)
Screen Reader Support
- Input type and constraints are announced
- Error messages are announced when validation fails
- Help text provides additional context when focused
JavaScript API
The Input component uses a validation controller for real-time validation feedback.
Stimulus Actions
Use these actions via data-action attributes:
| Action | Description |
|---|---|
input-validation#validate |
Validates input and shows feedback (called on input/blur) |
input-validation#togglePassword |
Toggles password visibility (show/hide) |
input-validation#formatPhoneNumber |
Formats phone as (XXX) XXX-XXXX |
input-validation#formatUrl |
Auto-adds https:// prefix on blur |
<%# Real-time validation is automatic for rui_input %>
<%= rui_input(
name: :email,
type: :email,
label: "Email",
required: true
) %>
<%# Password toggle is automatic for type: :password %>
<%= rui_input(
name: :password,
type: :password,
label: "Password"
) %>
<%# Phone formatting is automatic for type: :tel %>
<%= rui_input(
name: :phone,
type: :tel,
label: "Phone"
) %>
Programmatic Control
Control input validation from JavaScript:
// Get the input validation controller
const inputElement = document.querySelector('[data-controller="input-validation"]')
const validationCtrl = application.getControllerForElementAndIdentifier(
inputElement,
"input-validation"
)
// Trigger validation manually
validationCtrl.validate()
// Clear validation state
validationCtrl.clearValidation()
// Toggle password visibility
validationCtrl.togglePassword()
// Access the input directly
const input = validationCtrl.inputTarget
input.value = "new value"
validationCtrl.validate()
API Reference
rui_input
Text-based form input with validation, prefix/suffix slots, and password toggle
| Parameter | Type | Default | Description |
|---|---|---|---|
| name* | Symbol/String | — | Input name attribute (required for standalone) |
| type | Symbol |
:text
|
Input type
:text
:email
:password
:number
:tel
:url
:search
|
| value | String | — | Input value (auto-retrieved from object if bound) |
| placeholder | String | — | Placeholder text |
| label | String | — | Label text displayed above input |
| help_text | String | — | Help text displayed below input |
| required | Boolean |
false
|
Show required indicator (*) and set aria-required |
| disabled | Boolean |
false
|
Disable the input |
| readonly | Boolean |
false
|
Make input read-only |
Appearance
Visual styling options
| Parameter | Type | Default | Description |
|---|---|---|---|
| variant | Symbol |
:default
|
Visual style variant
:default
:filled
:outline
|
| color | Symbol |
:default
|
Focus ring color (semantic or any Tailwind color)
:default
:primary
:secondary
:success
:warning
:danger
:info
|
| size | Symbol |
:base
|
Input size
:xs
:sm
:base
:lg
:xl
|
| shape | Symbol |
:rounded
|
Border radius style
:square
:rounded
:pill
|
Validation
HTML5 validation attributes
| Parameter | Type | Default | Description |
|---|---|---|---|
| pattern | String | — | Regex pattern for validation |
| minlength | Integer | — | Minimum character length |
| maxlength | Integer | — | Maximum character length |
| min | Numeric | — | Minimum value (number inputs only) |
| max | Numeric | — | Maximum value (number inputs only) |
| step | Numeric/String | — | Step increment (number inputs, use 'any' for decimals) |
Behavior
Input behavior and accessibility hints
| Parameter | Type | Default | Description |
|---|---|---|---|
| autocomplete | String/Symbol | — | Browser autocomplete hint (e.g., 'email', 'username', 'off') |
| inputmode | String/Symbol | — | Virtual keyboard hint (e.g., 'numeric', 'email', 'tel') |
| list | String | — | ID of datalist element for autocomplete suggestions |
| autoformat | Boolean |
true
|
Auto-format type: :tel (NANP) / :url (https:// prefix) as the user types. Set false for international phone numbers or to disable entirely |
Custom Styling
Override default styles with custom Tailwind classes. Uses tailwind_merge for intelligent class merging.
| Parameter | Type | Default | Description |
|---|---|---|---|
| class | String | — | Custom classes for the input element (overrides defaults) |
| wrapper_class | String | — | Custom classes for the outer container |
| label_class | String | — | Custom classes for the label element |
| help_text_class | String | — | Custom classes for the help text |
| prefix_class | String | — | Custom classes for the prefix container |
| suffix_class | String | — | Custom classes for the suffix container |
| container_class | String | — | Custom classes for input container (when has prefix/suffix) |
Form Builder
Options when used with Rails form builder (f.rui_input)
| Parameter | Type | Default | Description |
|---|---|---|---|
| form | FormBuilder | — | Rails form builder instance (auto-set by f.rui_input) |
| object | Object/Symbol | — | Object to bind input to |
| method | Symbol | — | Attribute name on the object |
Slots
Content slots for customizing component parts
| Slot | Description |
|---|---|
| prefix | Leading content (icon or text). Positioned inside the input on the left. |
| suffix | Trailing content (icon or text). Note: Password inputs use this slot for the toggle button automatically. |
JavaScript & Namespacing
Input ships a Stimulus controller registered under the identifier input-validation.
If you set config.stimulus_namespace = "rui"
(
see guide
),
every emitted Stimulus attribute is prefixed: the identifier becomes rui--input-validation.
💡 Working with your own Stimulus controllers? Read Stimulus Namespace to keep the gem's controllers from colliding with yours.
JS registration (app/javascript/controllers/index.js)
import InputValidationController from "./rapid_rails_ui/input_validation_controller"
application.register("input-validation", InputValidationController)
import InputValidationController from "./rapid_rails_ui/input_validation_controller"
application.register("rui--input-validation", InputValidationController)
Custom trigger button
<button data-action="input-validation#open">Trigger</button>
<button data-action="rui--input-validation#open">Trigger</button>
<button data-action="<%= stimulus_action("<%= controller_id %>", "click->#open") %>">Trigger</button>