Form Components — Maquina Components
Form components for Rails. Inputs, selects, checkboxes styled with data attributes. Works with Rails form helpers. Copy, paste, customize.
Quick Reference
Components
| Component | Element | Description |
|---|---|---|
label |
<label> |
Form field label |
input |
<input> |
Text input field |
textarea |
<textarea> |
Multi-line text input |
select |
<select> |
Dropdown select |
checkbox |
<input type="checkbox"> |
Checkbox input |
radio |
<input type="radio"> |
Radio button input |
switch |
<input type="checkbox"> |
Toggle switch (styled checkbox) |
button |
<button> / <input type="submit"> |
Form button |
Form Structure
| Attribute | Element | Description |
|---|---|---|
data-component="form" |
<form> |
Optional form wrapper styling |
data-form-part="group" |
<div> |
Field group container |
data-form-part="description" |
<p> |
Help text below field |
data-form-part="error" |
<p> |
Error message |
Data Attributes
Input Components
| Attribute | Values | Description |
|---|---|---|
data-component="input" |
— | Text input styling |
data-component="textarea" |
— | Textarea styling |
data-component="select" |
— | Select dropdown styling |
data-component="checkbox" |
— | Checkbox styling |
data-component="radio" |
— | Radio button styling |
data-component="switch" |
— | Toggle switch styling |
data-component="label" |
— | Label styling |
Button Variants
| Attribute | Values |
|---|---|
data-component="button" |
Required for button styling |
data-variant |
default, primary, secondary, destructive, outline, ghost, link |
data-size |
sm, default, lg, icon, icon-sm, icon-lg |
States (Automatic)
| Attribute | Description |
|---|---|
aria-invalid="true" |
Applied by Rails on validation errors |
:disabled |
Standard HTML disabled state |
:required |
Standard HTML required state |
Basic Usage
<%= form_with model: @user, data: { component: "form" } do |f| %>
<div data-form-part="group">
<%= f.label :email, data: { component: "label" } %>
<%= f.email_field :email, data: { component: "input" }, placeholder: "you@example.com" %>
<p data-form-part="description">We'll never share your email.</p>
</div>
<div data-form-part="group">
<%= f.label :password, data: { component: "label" } %>
<%= f.password_field :password, data: { component: "input" } %>
</div>
<%= f.submit "Sign up", data: { component: "button", variant: "primary" } %>
<% end %>
Examples
All Input Types
<%= f.text_field :name, data: { component: "input" }, placeholder: "Full name" %>
<%= f.email_field :email, data: { component: "input" }, placeholder: "Email" %>
<%= f.password_field :password, data: { component: "input" } %>
<%= f.number_field :quantity, data: { component: "input" }, min: 1, max: 100 %>
<%= f.url_field :website, data: { component: "input" }, placeholder: "https://" %>
<%= f.search_field :query, data: { component: "input" }, placeholder: "Search..." %>
Textarea
<%= f.text_area :bio, data: { component: "textarea" }, rows: 4, placeholder: "Tell us about yourself..." %>
Select
<%= f.select :role,
[["Admin", "admin"], ["User", "user"], ["Guest", "guest"]],
{ include_blank: "Select a role" },
data: { component: "select" } %>
Checkbox
<label class="flex items-center gap-2">
<%= f.check_box :remember_me, data: { component: "checkbox" } %>
<span class="text-sm">Remember me</span>
</label>
Checkbox Group
<fieldset>
<legend class="text-sm font-medium mb-2">Notifications</legend>
<div class="space-y-2">
<label class="flex items-center gap-2">
<%= f.check_box :email_notifications, data: { component: "checkbox" } %>
<span class="text-sm">Email notifications</span>
</label>
<label class="flex items-center gap-2">
<%= f.check_box :sms_notifications, data: { component: "checkbox" } %>
<span class="text-sm">SMS notifications</span>
</label>
</div>
</fieldset>
Radio Buttons
<fieldset>
<legend class="text-sm font-medium mb-2">Plan</legend>
<div class="space-y-2">
<% %w[free pro enterprise].each do |plan| %>
<label class="flex items-center gap-2">
<%= f.radio_button :plan, plan, data: { component: "radio" } %>
<span class="text-sm"><%= plan.capitalize %></span>
</label>
<% end %>
</div>
</fieldset>
Switch (Toggle)
<label class="flex items-center gap-3">
<%= f.check_box :dark_mode, data: { component: "switch" } %>
<span class="text-sm">Enable dark mode</span>
</label>
Buttons
<%= f.submit "Save changes", data: { component: "button", variant: "primary" } %>
<button type="button" data-component="button" data-variant="secondary">
Cancel
</button>
<%= button_to "Delete", resource_path,
method: :delete,
data: { component: "button", variant: "destructive" } %>
<button type="button" data-component="button" data-variant="ghost" data-size="icon">
<%= icon_for :settings, class: "size-4" %>
</button>
<button type="submit" data-component="button" data-variant="primary">
<%= icon_for :save, class: "size-4" %>
Save
</button>
With Validation Errors
Rails automatically adds aria-invalid="true" when there are validation errors:
<div data-form-part="group">
<%= f.label :email, data: { component: "label" } %>
<%= f.email_field :email, data: { component: "input" } %>
<% if @user.errors[:email].any? %>
<p data-form-part="error"><%= @user.errors[:email].first %></p>
<% end %>
</div>
Field with Description
<div data-form-part="group">
<%= f.label :username, data: { component: "label" } %>
<%= f.text_field :username, data: { component: "input" }, placeholder: "johndoe" %>
<p data-form-part="description">This will be your public display name.</p>
</div>
Real-World Patterns
Login Form
<%= form_with url: sessions_path, data: { component: "form" } do |f| %>
<div class="space-y-4">
<div data-form-part="group">
<%= f.label :email, data: { component: "label" } %>
<%= f.email_field :email, data: { component: "input" }, placeholder: "you@example.com", required: true %>
</div>
<div data-form-part="group">
<%= f.label :password, data: { component: "label" } %>
<%= f.password_field :password, data: { component: "input" }, required: true %>
</div>
<div class="flex items-center justify-between">
<label class="flex items-center gap-2">
<%= f.check_box :remember_me, data: { component: "checkbox" } %>
<span class="text-sm">Remember me</span>
</label>
<%= link_to "Forgot password?", forgot_password_path, class: "text-sm text-primary hover:underline" %>
</div>
<%= f.submit "Sign in", data: { component: "button", variant: "primary" }, class: "w-full" %>
</div>
<% end %>
Settings Form
<%= form_with model: @settings, data: { component: "form" } do |f| %>
<div class="space-y-6">
<div data-form-part="group">
<%= f.label :display_name, data: { component: "label" } %>
<%= f.text_field :display_name, data: { component: "input" } %>
</div>
<div data-form-part="group">
<%= f.label :bio, data: { component: "label" } %>
<%= f.text_area :bio, data: { component: "textarea" }, rows: 3 %>
<p data-form-part="description">Brief description for your profile.</p>
</div>
<div data-form-part="group">
<%= f.label :timezone, data: { component: "label" } %>
<%= f.time_zone_select :timezone, nil, {}, data: { component: "select" } %>
</div>
<div class="border-t pt-6">
<h3 class="text-sm font-medium mb-4">Notifications</h3>
<div class="space-y-4">
<label class="flex items-center justify-between">
<div>
<span class="text-sm font-medium">Email notifications</span>
<p class="text-sm text-muted-foreground">Receive emails about activity.</p>
</div>
<%= f.check_box :email_notifications, data: { component: "switch" } %>
</label>
<label class="flex items-center justify-between">
<div>
<span class="text-sm font-medium">Marketing emails</span>
<p class="text-sm text-muted-foreground">Receive tips and updates.</p>
</div>
<%= f.check_box :marketing_emails, data: { component: "switch" } %>
</label>
</div>
</div>
<div class="flex gap-3">
<%= f.submit "Save changes", data: { component: "button", variant: "primary" } %>
<%= link_to "Cancel", settings_path, data: { component: "button", variant: "outline" } %>
</div>
</div>
<% end %>
Inline Form
<%= form_with url: search_path, method: :get, class: "flex gap-2" do |f| %>
<%= f.search_field :q, data: { component: "input" }, placeholder: "Search...", class: "flex-1" %>
<button type="submit" data-component="button" data-variant="primary">
<%= icon_for :search, class: "size-4" %>
</button>
<% end %>
Theme Variables
/* Inputs */
var(--background)
var(--foreground)
var(--border)
var(--input)
var(--muted-foreground)
var(--destructive)
/* Focus ring */
var(--ring)
/* Buttons */
var(--primary)
var(--primary-foreground)
var(--secondary)
var(--secondary-foreground)
var(--destructive)
var(--destructive-foreground)
var(--accent)
var(--accent-foreground)
Customization
Custom Input Sizes
Add to your CSS:
[data-component="input"][data-size="sm"] {
@apply h-8 text-sm px-2;
}
[data-component="input"][data-size="lg"] {
@apply h-12 text-base px-4;
}
Input with Icon
<div class="relative">
<%= icon_for :search, class: "absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" %>
<%= f.search_field :q, data: { component: "input" }, class: "pl-10", placeholder: "Search..." %>
</div>
Input with Button
<div class="flex">
<%= f.text_field :code, data: { component: "input" }, class: "rounded-r-none", placeholder: "Enter code" %>
<button type="submit" data-component="button" data-variant="primary" class="rounded-l-none">
Apply
</button>
</div>
Handling Rails Validation
field_with_errors Wrapper
Rails wraps invalid fields in a <div class="field_with_errors">. The CSS handles this:
.field_with_errors {
display: contents;
}
This prevents layout disruption while preserving the error styling via aria-invalid.
Error Display Pattern
<div data-form-part="group">
<%= f.label :email, data: { component: "label" } %>
<%= f.email_field :email, data: { component: "input" } %>
<% if @user.errors[:email].any? %>
<p data-form-part="error">
<%= icon_for :alert_circle, class: "size-3 inline-block mr-1" %>
<%= @user.errors[:email].first %>
</p>
<% end %>
</div>
Accessibility
- All inputs should have associated labels
- Error messages are styled distinctively (color + icon)
- Focus states are clearly visible
- Disabled states reduce opacity
- Required fields can use HTML
requiredattribute aria-invalidis automatically set by Rails
File Structure
app/assets/stylesheets/form.css
docs/form.md
Note: Form components are CSS-only — no partials or helpers needed. Just use Rails form helpers with the appropriate data-component attributes.