Form Layouts

Multi-field form structures for settings, onboarding, and CRUD views

5 variants
Variant 1

Profile Settings

Manage your account information and preferences.

Personal Information

Update your name and contact details.

Address

Your billing and shipping address.

Notifications

Choose how you want to be notified.

Receive updates about your account via email

Get text messages for important alerts

Receive tips, product updates and promotions

Variant 1 — Source code

<%# Variant 1: Profile settings — classic 2-column layout with sidebar labels %>
<section class="bg-background py-16 lg:py-24">
  <div class="mx-auto max-w-4xl px-6">
    <div class="mb-8">
      <h2 class="text-foreground text-2xl font-bold tracking-tight">Profile Settings</h2>
      <p class="text-muted-foreground mt-1 text-sm">Manage your account information and preferences.</p>
    </div>

    <%= form_with url: root_path, class: "!gap-0 divide-y divide-border" do |f| %>
      <%# Personal Information %>
      <div class="grid grid-cols-1 gap-x-8 gap-y-6 py-8 lg:grid-cols-3">
        <div>
          <h3 class="text-foreground font-semibold">Personal Information</h3>
          <p class="text-muted-foreground mt-1 text-sm">Update your name and contact details.</p>
        </div>
        <div class="lg:col-span-2 flex flex-col gap-4">
          <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
            <%= f.text_field :first_name, label: "First name", placeholder: "Jane" %>
            <%= f.text_field :last_name, label: "Last name", placeholder: "Doe" %>
          </div>
          <%= f.email_field :email, label: "Email address", placeholder: "jane@example.com" %>
          <%= f.phone_field :phone, label: "Phone number", placeholder: "+1 (555) 123-4567" %>
        </div>
      </div>

      <%# Address %>
      <div class="grid grid-cols-1 gap-x-8 gap-y-6 py-8 lg:grid-cols-3">
        <div>
          <h3 class="text-foreground font-semibold">Address</h3>
          <p class="text-muted-foreground mt-1 text-sm">Your billing and shipping address.</p>
        </div>
        <div class="lg:col-span-2 flex flex-col gap-4">
          <%= f.text_field :street, label: "Street address", placeholder: "123 Main St" %>
          <div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
            <%= f.text_field :city, label: "City", placeholder: "San Francisco" %>
            <%= f.select :state, options_for_select([["California", "CA"], ["New York", "NY"], ["Texas", "TX"]]), { include_blank: "Select..." }, { label: "State" } %>
            <%= f.text_field :zip, label: "ZIP code", placeholder: "94102" %>
          </div>
        </div>
      </div>

      <%# Notifications %>
      <div class="grid grid-cols-1 gap-x-8 gap-y-6 py-8 lg:grid-cols-3">
        <div>
          <h3 class="text-foreground font-semibold">Notifications</h3>
          <p class="text-muted-foreground mt-1 text-sm">Choose how you want to be notified.</p>
        </div>
        <div class="lg:col-span-2 flex flex-col gap-4">
          <%= f.toggler :email_notifications, label: "Email notifications", hint: "Receive updates about your account via email" %>
          <%= f.toggler :sms_notifications, label: "SMS notifications", hint: "Get text messages for important alerts" %>
          <%= f.toggler :marketing_emails, label: "Marketing emails", hint: "Receive tips, product updates and promotions" %>
        </div>
      </div>

      <%# Actions %>
      <div class="flex justify-end gap-3 pt-8">
        <%= ui.btn "Cancel", variant: :secondary %>
        <%= ui.btn "Save changes" %>
      </div>
    <% end %>
  </div>
</section>
Variant 2

Create Project

Set up a new project with all the details.

General Basic project information

Markdown is supported

Timeline Set project dates and milestones

Optional — leave blank if not applicable

Access & Visibility Control who can see and edit this project

Team members can leave comments on tasks

Notify team members about project updates

Variant 2 — Source code

<%# Variant 2: Card-based form — form sections in separate cards %>
<section class="bg-muted/30 py-16 lg:py-24">
  <div class="mx-auto max-w-3xl px-6">
    <div class="mb-8">
      <h2 class="text-foreground text-2xl font-bold tracking-tight">Create Project</h2>
      <p class="text-muted-foreground mt-1 text-sm">Set up a new project with all the details.</p>
    </div>

    <%= form_with url: root_path, class: "flex flex-col gap-6" do |f| %>
      <%# General %>
      <%= ui.card do %>
        <%= ui.card_header do %>
          <%= ui.card_title "General" %>
          <%= ui.card_subtitle "Basic project information" %>
        <% end %>
        <%= ui.card_body class: "flex flex-col gap-4" do %>
          <%= f.text_field :name, label: "Project name", placeholder: "My Awesome Project" %>
          <%= f.text_area :description, label: "Description", placeholder: "Describe what this project is about...", hint: "Markdown is supported" %>
          <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
            <%= f.select :category, options_for_select([["Engineering", "eng"], ["Design", "design"], ["Marketing", "mkt"], ["Product", "product"]]), { include_blank: "Select category..." }, { label: "Category" } %>
            <%= f.select :priority, options_for_select([["Low", "low"], ["Medium", "medium"], ["High", "high"], ["Critical", "critical"]]), { include_blank: "Select..." }, { label: "Priority" } %>
          </div>
        <% end %>
      <% end %>

      <%# Timeline %>
      <%= ui.card do %>
        <%= ui.card_header do %>
          <%= ui.card_title "Timeline" %>
          <%= ui.card_subtitle "Set project dates and milestones" %>
        <% end %>
        <%= ui.card_body class: "flex flex-col gap-4" do %>
          <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
            <%= f.date_field :start_date, label: "Start date" %>
            <%= f.date_field :end_date, label: "End date" %>
          </div>
          <%= f.text_field :budget, label: "Budget", placeholder: "$0.00", hint: "Optional — leave blank if not applicable" %>
        <% end %>
      <% end %>

      <%# Access %>
      <%= ui.card do %>
        <%= ui.card_header do %>
          <%= ui.card_title "Access & Visibility" %>
          <%= ui.card_subtitle "Control who can see and edit this project" %>
        <% end %>
        <%= ui.card_body class: "flex flex-col gap-4" do %>
          <%= f.select :visibility, options_for_select([["Public — anyone in workspace", "public"], ["Private — invite only", "private"], ["Team — team members only", "team"]]), {}, { label: "Visibility" } %>
          <%= f.toggler :allow_comments, label: "Allow comments", hint: "Team members can leave comments on tasks" %>
          <%= f.toggler :send_notifications, label: "Send notifications", hint: "Notify team members about project updates" %>
        <% end %>
      <% end %>

      <%# Actions %>
      <div class="flex justify-end gap-3">
        <%= ui.btn "Cancel", variant: :secondary %>
        <%= ui.btn "Create project" %>
      </div>
    <% end %>
  </div>
</section>
Variant 3
Contact Us Fill out the form below and we'll get back to you within 24 hours.

Variant 3 — Source code

<%# Variant 3: Compact single-card form — contact / support form %>
<section class="bg-background py-16 lg:py-24">
  <div class="mx-auto max-w-xl px-6">
    <%= ui.card do %>
      <%= ui.card_header do %>
        <%= ui.card_title "Contact Us" %>
        <%= ui.card_subtitle "Fill out the form below and we'll get back to you within 24 hours." %>
      <% end %>
      <%= ui.card_body class: "flex flex-col gap-4" do %>
        <%= form_with url: root_path, class: "flex flex-col gap-4" do |f| %>
          <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
            <%= f.text_field :first_name, label: "First name", placeholder: "Jane" %>
            <%= f.text_field :last_name, label: "Last name", placeholder: "Doe" %>
          </div>
          <%= f.email_field :email, label: "Email", placeholder: "jane@example.com" %>
          <%= f.select :subject, options_for_select(["General inquiry", "Technical support", "Billing question", "Partnership", "Other"]), { include_blank: "Select a topic..." }, { label: "Subject" } %>
          <%= f.text_area :message, label: "Message", placeholder: "Tell us how we can help...", rows: 5 %>
          <%= f.check_box :agree, label: "I agree to the privacy policy and terms of service" %>
          <%= ui.btn "Send message", block: true %>
        <% end %>
      <% end %>
    <% end %>
  </div>
</section>
Variant 4

Create Your Account

Complete your profile to get started.

Company Details Tell us about your organization.

Variant 4 — Source code

<%# Variant 4: Stepped form with tabs — onboarding / wizard style %>
<section class="bg-background py-16 lg:py-24">
  <div class="mx-auto max-w-3xl px-6">
    <div class="mb-8 text-center">
      <h2 class="text-foreground text-2xl font-bold tracking-tight">Create Your Account</h2>
      <p class="text-muted-foreground mt-1 text-sm">Complete your profile to get started.</p>
    </div>

    <%# Step indicators %>
    <%= ui.stepper class: "mb-10" do %>
      <%= ui.stepper_step "Personal", status: :completed %>
      <%= ui.stepper_step "Company", status: :current %>
      <%= ui.stepper_step "Preferences", status: :pending %>
    <% end %>

    <%= ui.card do %>
      <%= ui.card_header do %>
        <%= ui.card_title "Company Details" %>
        <%= ui.card_subtitle "Tell us about your organization." %>
      <% end %>
      <%= ui.card_body class: "flex flex-col gap-4" do %>
        <%= form_with url: root_path, class: "flex flex-col gap-4" do |f| %>
          <%= f.text_field :company_name, label: "Company name", placeholder: "Acme Inc." %>
          <%= f.url_field :website, label: "Website", placeholder: "https://example.com" %>
          <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
            <%= f.select :industry, options_for_select(["Technology", "Finance", "Healthcare", "Education", "E-commerce", "Other"]), { include_blank: "Select industry..." }, { label: "Industry" } %>
            <%= f.select :company_size, options_for_select(["1-10", "11-50", "51-200", "201-1000", "1000+"]), { include_blank: "Select size..." }, { label: "Company size" } %>
          </div>
          <%= f.text_area :about, label: "About", placeholder: "Brief description of your company...", rows: 3 %>
        <% end %>
      <% end %>
      <%= ui.card_footer do %>
        <div class="flex w-full justify-between">
          <%= ui.btn "Back", variant: :secondary %>
          <%= ui.btn do %>
            Continue
            <%= ui.icon "arrow-right", size: 5 %>
          <% end %>
        </div>
      <% end %>
    <% end %>
  </div>
</section>
Variant 5

Account Settings

Manage your account preferences and security.

Profile Your public profile information

JPG, PNG or GIF. Max 2MB.

Brief description for your profile. Max 160 characters.

Change Password Update your password to keep your account secure

Minimum 8 characters

Danger Zone Irreversible actions — proceed with caution

Delete account

Permanently delete your account and all associated data. This action cannot be undone.

Variant 5 — Source code

<%# Variant 5: Account settings — tabs + form with danger zone %>
<section class="bg-background py-16 lg:py-24">
  <div class="mx-auto max-w-4xl px-6">
    <div class="mb-8">
      <h2 class="text-foreground text-2xl font-bold tracking-tight">Account Settings</h2>
      <p class="text-muted-foreground mt-1 text-sm">Manage your account preferences and security.</p>
    </div>

    <%= ui.tabs variant: :underline, class: "mb-8" do %>
      <%= ui.tabs_item "General", href: "#", icon: "user", active: true %>
      <%= ui.tabs_item "Security", href: "#", icon: "shield-check" %>
      <%= ui.tabs_item "Billing", href: "#", icon: "credit-card" %>
      <%= ui.tabs_item "Notifications", href: "#", icon: "bell" %>
    <% end %>

    <%= form_with url: root_path, class: "flex flex-col gap-8" do |f| %>
      <%# Profile %>
      <%= ui.card do %>
        <%= ui.card_header do %>
          <%= ui.card_title "Profile" %>
          <%= ui.card_subtitle "Your public profile information" %>
        <% end %>
        <%= ui.card_body class: "flex flex-col gap-5" do %>
          <div class="flex items-center gap-4">
            <div class="flex size-16 items-center justify-center rounded-full bg-muted">
              <%= ui.icon "user", size: 8, class: "text-muted-foreground" %>
            </div>
            <div>
              <%= ui.btn "Upload photo", variant: :secondary, size: :sm %>
              <p class="text-muted-foreground mt-1 text-xs">JPG, PNG or GIF. Max 2MB.</p>
            </div>
          </div>
          <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
            <%= f.text_field :display_name, label: "Display name", placeholder: "Jane Doe" %>
            <%= f.text_field :username, label: "Username", placeholder: "janedoe" %>
          </div>
          <%= f.email_field :email, label: "Email", placeholder: "jane@example.com" %>
          <%= f.text_area :bio, label: "Bio", placeholder: "Tell us about yourself...", rows: 3, hint: "Brief description for your profile. Max 160 characters." %>
        <% end %>
        <%= ui.card_footer do %>
          <div class="flex w-full justify-end gap-3">
            <%= ui.btn "Cancel", variant: :secondary %>
            <%= ui.btn "Update profile" %>
          </div>
        <% end %>
      <% end %>

      <%# Password %>
      <%= ui.card do %>
        <%= ui.card_header do %>
          <%= ui.card_title "Change Password" %>
          <%= ui.card_subtitle "Update your password to keep your account secure" %>
        <% end %>
        <%= ui.card_body class: "flex flex-col gap-4" do %>
          <%= f.password_field :current_password, label: "Current password" %>
          <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
            <%= f.password_field :new_password, label: "New password", hint: "Minimum 8 characters" %>
            <%= f.password_field :confirm_password, label: "Confirm new password" %>
          </div>
        <% end %>
        <%= ui.card_footer do %>
          <div class="flex w-full justify-end">
            <%= ui.btn "Update password" %>
          </div>
        <% end %>
      <% end %>

      <%# Danger zone %>
      <%= ui.card class: "border-destructive/50" do %>
        <%= ui.card_header do %>
          <%= ui.card_title do %>
            <span class="text-destructive">Danger Zone</span>
          <% end %>
          <%= ui.card_subtitle "Irreversible actions — proceed with caution" %>
        <% end %>
        <%= ui.card_body do %>
          <div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
            <div>
              <p class="text-foreground text-sm font-medium">Delete account</p>
              <p class="text-muted-foreground text-sm">Permanently delete your account and all associated data. This action cannot be undone.</p>
            </div>
            <%= ui.btn "Delete account", variant: :danger, size: :sm %>
          </div>
        <% end %>
      <% end %>
    <% end %>
  </div>
</section>

Are you absolutely sure?

This cannot be undone.