Forms Guide

Learn how to use RailsKit form components to build accessible, responsive forms with automatic error handling.

Overview

RailsKit form components provide a comprehensive set of inputs designed for modern Rails applications. Each component supports both Rails form builder integration and standalone usage, giving you flexibility in how you build your forms.

  • Automatic error detection — Components read errors directly from your form object
  • Dark mode support — All components include dark: variants
  • Accessibility built-in — Proper labels, ARIA attributes, and keyboard navigation
  • Consistent styling — Tailwind-only classes for easy customization

Dual-Mode Pattern

Every form component supports two modes of operation. The component automatically detects which mode to use based on the parameters you provide.

Form Builder Mode

Pass form: and attribute: to integrate with Rails form objects.

Standalone Mode

Pass name: and value: for forms without a backing model.

Form Builder Mode

Use form builder mode when working with Rails models. The component will automatically:

  • Generate the correct name attribute
  • Populate the current value from the model
  • Display validation errors from the model
  • Generate accessible id attributes for labels
ViewComponent Example
<%= form_with model: @user do |f| %>
  <%= render TextFieldComponent.new(
    form: f,
    attribute: :email,
    label: "Email address",
    type: "email",
    required: true,
    hint: "We'll never share your email"
  ) %>

  <%= render TextFieldComponent.new(
    form: f,
    attribute: :password,
    type: "password",
    required: true
  ) %>

  <%= render CheckboxComponent.new(
    form: f,
    attribute: :remember_me,
    label: "Remember me",
    description: "Stay signed in for 30 days"
  ) %>
<% end %>
Classic Rails Example
<%= form_with model: @user do |f| %>
  <%= render "components/text_field",
      form: f,
      attribute: :email,
      label: "Email address",
      type: "email",
      required: true %>
<% end %>

Standalone Mode

Use standalone mode for search forms, filters, or any form that doesn't have a backing model.

Standalone Example
<%= render TextFieldComponent.new(
  name: "search",
  placeholder: "Search components...",
  type: "search"
) %>

<%= render SelectComponent.new(
  name: "category",
  label: "Category",
  options: ["All", "Navigation", "Forms", "Overlays"],
  selected: "All"
) %>

Error Handling

Form components automatically detect and display errors in two ways:

Automatic Detection (Form Builder Mode)

When using form builder mode, components automatically check form.object.errors for the specified attribute. If errors exist, the component displays error styling and the first error message.

Automatic Error Detection
# In your controller
def create
  @user = User.new(user_params)
  unless @user.save
    render :new, status: :unprocessable_entity
  end
end

# In your view - errors display automatically!
<%= render TextFieldComponent.new(
  form: f,
  attribute: :email
) %>

Manual Errors

You can also pass errors manually using the error: parameter. This is useful for client-side validation or custom error scenarios.

Manual Error
<%= render TextFieldComponent.new(
  name: "api_key",
  label: "API Key",
  error: "Invalid API key format"
) %>

Accessibility

All form components are built with accessibility in mind:

  • Labels — Every input has a properly associated <label> element
  • Required fields — Required fields include both a visual indicator and the required attribute
  • Error association — Error messages are connected via aria-describedby
  • Focus states — Clear focus rings using focus-visible: for keyboard users
  • Keyboard navigation — Interactive components like Combobox support full keyboard control
Generated HTML Structure
<div class="w-full">
  <label for="user_email" class="...">
    Email
    <span class="text-red-500">*</span>
  </label>
  <input
    type="email"
    name="user[email]"
    id="user_email"
    required
    aria-describedby="user_email_error"
    class="..."
  />
  <p id="user_email_error" class="...">
    can't be blank
  </p>
</div>

Available Components

RailsKit includes a comprehensive set of form components:

Text Field

Single-line text input with label, hint, and error states

Textarea

Multi-line text with auto-resize and character counter

Select

Native dropdown select with option groups

Checkbox

Single checkbox with label and description

Radio Group

Group of mutually exclusive radio options

Checkbox Group

Group of checkboxes for multi-select

Switch

Toggle switch for boolean values

Input Group

Input with prefix/suffix addons

File Upload

Drag-and-drop file upload zone

Combobox

Searchable select with keyboard navigation

Examples

Login Form

Login Form
<%= form_with url: session_path, method: :post do |f| %>
  <%= render TextFieldComponent.new(
    name: "email",
    label: "Email",
    type: "email",
    required: true,
    placeholder: "[email protected]"
  ) %>

  <%= render TextFieldComponent.new(
    name: "password",
    label: "Password",
    type: "password",
    required: true
  ) %>

  <div class="flex items-center justify-between">
    <%= render CheckboxComponent.new(
      name: "remember_me",
      label: "Remember me"
    ) %>

    <%= link_to "Forgot password?", "#", class: "text-sm text-slate-600 hover:text-slate-900" %>
  </div>

  <button type="submit" class="w-full ...">
    Sign in
  </button>
<% end %>

Settings Form

Settings Form
<%= form_with model: @settings do |f| %>
  <%= render TextFieldComponent.new(
    form: f,
    attribute: :display_name,
    label: "Display name",
    hint: "This is how your name will appear to others"
  ) %>

  <%= render TextareaComponent.new(
    form: f,
    attribute: :bio,
    label: "Bio",
    rows: 4,
    max_length: 500,
    hint: "Tell us about yourself"
  ) %>

  <%= render ComboboxComponent.new(
    form: f,
    attribute: :timezone,
    label: "Timezone",
    options: ActiveSupport::TimeZone.all.map { |tz| [tz.name, tz.tzinfo.name] },
    placeholder: "Select your timezone..."
  ) %>

  <%= render SwitchComponent.new(
    form: f,
    attribute: :email_notifications,
    label: "Email notifications",
    description: "Receive email updates about your account"
  ) %>

  <%= render SwitchComponent.new(
    form: f,
    attribute: :marketing_emails,
    label: "Marketing emails",
    description: "Receive tips and product announcements"
  ) %>
<% end %>

Contact Form

Contact Form
<%= form_with url: contact_path, method: :post do |f| %>
  <div class="grid grid-cols-2 gap-4">
    <%= render TextFieldComponent.new(
      name: "first_name",
      label: "First name",
      required: true
    ) %>

    <%= render TextFieldComponent.new(
      name: "last_name",
      label: "Last name",
      required: true
    ) %>
  </div>

  <%= render InputGroupComponent.new(
    name: "email",
    label: "Email",
    type: "email",
    trailing_addon: "@company.com",
    required: true
  ) %>

  <%= render SelectComponent.new(
    name: "subject",
    label: "Subject",
    options: ["General inquiry", "Technical support", "Sales", "Partnership"],
    include_blank: "Select a subject..."
  ) %>

  <%= render TextareaComponent.new(
    name: "message",
    label: "Message",
    rows: 6,
    required: true,
    placeholder: "How can we help you?"
  ) %>

  <%= render FileUploadComponent.new(
    name: "attachments",
    label: "Attachments",
    multiple: true,
    accept: ".pdf,.doc,.docx,.png,.jpg",
    hint: "Upload any relevant files (max 10MB each)"
  ) %>
<% end %>