Formularios
Estado: EstableFormulario
Ui::FormBuilder: migrar un formulario del admin es agregar builder: Ui::FormBuilder al form_with y escribir f.field :capacity, label: "Capacidad". Cada campo sale con su etiqueta, ayuda, asterisco (si el modelo valida presencia), el error del modelo y el control que le toca (campo de texto, área de texto, select, tarjetas). f.error_summary resume los errores arriba y f.submit tiene el look del botón.
- Form builder
- Formularios
- Rails
app/views/… (la demo de arriba)
<%# Un formulario "Nueva clase" con Ui::FormBuilder sobre un modelo de Active
Model (Ui::Demo::SessionForm) con dos errores puestos a mano: el builder
lee sus tipos (fecha, hora, número) y sus validaciones (lo requerido). En
la tarjeta del índice se ven el resumen, la plantilla, los números y el botón. %>
<%
templates = [ [ "Vinyasa Flow", 1 ], [ "Reformer Básico", 2 ], [ "Barre Sculpt", 3 ], [ "Yin & Meditación", 4 ] ]
hosts = { "Titulares" => [ [ "Ana Robles", 11 ], [ "Mariana Ortiz", 12 ], [ "Sofía Treviño", 13 ] ],
"Suplentes" => [ [ "Lucía Gamboa", 21 ], [ "Renata Lira", 22 ] ] }
clase = Ui::Demo::SessionForm.new(template_id: 2, host_id: 12, date: Date.new(2026, 10, 19),
start_time: "07:00", end_time: "06:50", minutes: 50)
clase.errors.add(:capacity, :blank)
clase.errors.add(:end_time, "debe ser después de la hora de inicio")
%>
<div class="@container w-full max-w-xl">
<%= form_with model: clase, url: ui_component_path("form"), method: :get, builder: Ui::FormBuilder,
namespace: "form-demo", class: "grid gap-5" do |f| %>
<%= f.error_summary %>
<%= f.field :template_id, label: "Plantilla de clase", choices: templates, prompt: "Elige una plantilla" %>
<div class="hidden gap-5 @md:grid">
<%= f.field :host_id, label: "Quién la da", choices: hosts, prompt: "Elige a alguien del equipo" %>
<%= f.field :substitute_name, label: "Suplente", optional: true, placeholder: "Nombre de quien suple",
hint: "Aparece en el horario público. Déjalo vacío para usar a la persona titular." %>
<div class="grid grid-cols-3 gap-3">
<%= f.field :date, label: "Fecha" %>
<%= f.field :start_time, label: "Inicio" %>
<%= f.field :end_time, label: "Fin" %>
</div>
</div>
<div class="grid grid-cols-2 gap-3">
<%= f.field :minutes, label: "Duración", trailing: "min", min: 15, step: 5 %>
<%= f.field :capacity, label: "Capacidad", trailing: "lugares", min: 1, placeholder: "14" %>
</div>
<div class="flex items-center gap-2 pt-1">
<%= f.submit "Crear clase" %>
<%= link_to "Cancelar", ui_component_path("form"), class: Ui::ButtonComponent.classes(variant: :ghost) %>
</div>
<% end %>
</div>Qué hace
- Sin as:, el tipo se deduce: choices/collection → select; email, password, phone, url por el nombre; número, fecha, hora, texto largo o booleano por la columna.
- El asterisco y required salen de las validaciones de presencia sin condiciones (también de belongs_to para el _id).
- Los helpers nativos siguen igual: f.text_field, f.select, f.collection_select, f.text_area y f.label con class: propia no cambian; sin class: reciben la superficie de Eclipse UI.
- field_with_errors de Rails no rompe el acomodo de los controles.
Props
| Nombre | Tipo | Default | Descripción |
|---|---|---|---|
| f.field(attr, …) | Symbol + opciones | — | Renderiza Field + el control. Opciones: label, hint, required (se deduce), optional, placeholder, y las del componente. |
| as | Symbol | deducido | :text, :email, :password, :tel, :url, :search, :number, :date, :time, :datetime, :textarea, :select, :radio_cards, :checkbox, :switch. |
| choices / collection | Array | Hash / Enumerable | nil | Opciones del select (Hash → grupos). Con collection: value_method: (:id) y text_method: (:to_s). |
| include_blank / prompt | String | nil | Opción vacía del select, como en Rails. |
| leading / trailing / clearable / strength | — | — | Pasan a Ui::InputComponent ("$", "min", la X, el medidor). |
| rows / max_rows / maxlength | Integer | — | Pasan a Ui::TextareaComponent. |
| f.error_summary(title:) | — | "Revisa N campos antes de guardar" | Ui::AlertComponent destructive con la lista; cada error enlaza a su campo. Nada si no hay errores. |
| f.submit / f.button | value, variant:, size:, full_width: | variant: :primary | Con el look de Ui::ButtonComponent; con class: propia queda nativo. |
Accesibilidad
- Cada control queda enlazado a su etiqueta, ayuda y error (for, aria-describedby, aria-invalid).
- El resumen de errores es un role="alert" y cada error es un enlace al campo.
- Los controles son los nativos del navegador; sin JS el formulario se envía igual.