Esc

Components

All components Every component, with search. Accordion Sections that expand and collapse, one at a time or several. Alert A highlighted message, optionally dismissible. Avatar A person's picture, with initials when there is none. Avatar group Overlapping avatars with a "+N" count for the rest. Badge A small status label. Banner A dismissible strip across the top of a page. Breadcrumb The path to the current page, collapsing long trails. Button Buttons in every variant and size, with icons and a loading state. Button to A button that submits a one-off form, like Rails' button_to. Card A bordered container with header, content and footer. Checkbox A checkbox with label, standalone or bound to a form builder. Collapsible A section that opens and closes under its trigger, like a branch of a nav tree. Data grid A sortable, filterable, server-paged table on AG Grid. Date picker A typed date field with a calendar, on Cally. Dialog A modal dialog on the native <dialog> element. Divider A horizontal or vertical rule, optionally labelled. Form field A label, hint and error message wired to any form control. Icon A Lucide icon. Image An image with consistent sizing and corner radius. Input A text field with label, hint and error message, standalone or with a form builder. Input group A text field with addons before or after it. Label A form label with a required marker. Link A link styled as text or as a button. Menu A dropdown menu with keyboard navigation. Pagination Page links for a long list, from a Pagy object. Password input A password field with a show/hide toggle. Popover Floating content anchored to a trigger. Radio group A set of radio buttons. Native select The browser's own select, styled closed; best for phones' system pickers. Select A styled dropdown with keyboard navigation and type-ahead, posting like a native select. Sheet A panel that slides in from an edge of the screen. Sidebar A collapsible application sidebar with groups and menus. Spinner A loading indicator. Switch An on/off toggle. Table A styled table with header, rows and caption. Table of contents An "On this page" list that follows the reader's scroll. Tabs Tabbed panels with keyboard navigation. Text Body text in set sizes, weights and tones. Textarea A multi-line text field with label, hint and error, that can grow with its text. Theme toggle A light/dark/system theme switcher. Tooltip A hint shown on hover and focus. Typography Headings, lead, muted, code and blockquote text styles.

Building forms

Pass the form builder and the attribute, and each field takes its name, value, label and errors from your model. Validation works the Rails way: save, and on failure render the form again with a 422.

Bind a field to a model

Give a field form: and field: and it renders through the builder, so it gets a scoped name like user[email] and the model's current value.

app/views/users/_form.html.erb
<%= form_with model: @user do |f| %>
  <%= render(UI::Input.new(form: f, field: :email, type: :email, hint: "We'll only use it for receipts.")) %>
  <%= render(UI::Textarea.new(form: f, field: :bio)) %>
  <%= render(UI::Select.new(form: f, field: :plan, options: Plan::NAMES)) %>
<% end %>
  • The label is the attribute's human name, so activerecord.attributes translations apply. Pass label: to change it, or label: false to leave it out.
  • A hint (hint:) sits under the field, and screen readers read it with the field.
  • Input, Textarea, Select, Native select, Checkbox, Password input and Date picker all work this way. Form field adds the same label, hint and error to a control of your own.

Showing errors

A bound field shows the model's errors on its attribute: a red border, the message underneath, and aria-invalid with the message as its description. For a team_id field that includes belongs_to :team's "Team must exist". Nothing extra to write.

app/views/users/_form.html.erb
<%# From the model, automatically: %>
<%= render(UI::Input.new(form: f, field: :email)) %>

<%# Your own message, or none: %>
<%= render(UI::Input.new(form: f, field: :email, error: "Use your work email")) %>
<%= render(UI::Input.new(form: f, field: :email, error: false)) %>

Rails wraps every field that has an error in <div class="field_with_errors">, which can break a form's layout. The fields show errors themselves, so turn the wrapper off:

config/application.rb
# The fields show their own errors, so Rails needn't wrap each
# one in <div class="field_with_errors">.
config.action_view.field_error_proc = ->(html, _instance) { html }

Example: sign in

Submit it empty to see field errors, then try a wrong password for an error on the whole form. The demo account is ada@example.com with velvetkit.

Example: invite a teammate

A longer form with every kind of field. Each one has a validation that can fail: send it empty, pick a start date in the past, or leave the permission box unticked. The errors are listed at the top as well as under each field, and the field names come from the locale file, so a label and its error always match.

We'll send the invite here.

Type the date as DD/MM/YYYY, or use the calendar

Up to 160 characters, added to the invite email.

The view tabs show the exact templates running above. In the demo they post to this site and use a stand-in model with the same validations; the model and controller tabs show what they'd be in your app.