Formularios

Estado: Estable

Formulario

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.

No puede estar en blanco

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