Formularios
Estado: BetaTarjetas de opción
Radios nativos con forma de tarjeta para elegir entre pocas opciones que necesitan contexto: entrega, método de pago, plan. Cada tarjeta lleva título, descripción, un dato a la derecha (precio, tiempo) e ícono opcional. Al elegir, el marco del acento viaja con resorte a la nueva y su círculo se llena.
- Resorte
- Formularios
- Form builder
app/views/… (la demo de arriba)
<%# Las opciones de un pedido en el checkout: entrega y método de pago.
En la tarjeta del índice solo se ve la entrega; en el escenario, también el pago. %>
<div class="@container w-full max-w-2xl">
<div class="grid gap-8">
<%= render Ui::RadioCardsComponent.new(name: "order[delivery_method]", id: "radio-cards-demo-delivery", label: "Entrega",
selected: "shipping", required: true,
options: [
{ value: "shipping", title: "Envío a domicilio", description: "Llega en 2–4 días hábiles", meta: "$99" },
{ value: "pickup", title: "Recoger en el estudio", description: "Listo mañana en recepción", meta: "Gratis" }
]) %>
<%= render Ui::RadioCardsComponent.new(name: "order[payment_method]", id: "radio-cards-demo-payment", label: "Método de pago",
columns: 2, hint: "El link de pago llega por correo y vence en 24 horas.", class: "hidden @lg:grid") do |group| %>
<% group.with_option(value: "card", title: "Tarjeta", description: "Visa, Mastercard o AMEX", meta: "3 MSI", icon: :credit_card) %>
<% group.with_option(value: "stripe", title: "Link de pago", description: "Paga desde tu teléfono", icon: :link) %>
<% group.with_option(value: "cash", title: "Efectivo en recepción", description: "Antes de tu primera clase", icon: :banknote) %>
<% group.with_option(value: "oxxo", title: "OXXO", description: "Próximamente", disabled: true, icon: :store) %>
<% end %>
</div>
</div>Qué hace
- El marco viaja con cuatro resortes "snappy" (x, y, ancho, alto): en 2 columnas se mueve en diagonal y un cambio a medio camino sigue de donde va.
- El círculo se llena con el resorte "responsive" y aparece el punto; el de la anterior se vacía.
- Sin JS es un grupo de radios normal y la tarjeta elegida se marca con CSS (:has(:checked)).
- Se reacomoda si cambia el ancho o se resetea el formulario.
Props
| Nombre | Tipo | Default | Descripción |
|---|---|---|---|
| name | String | nil | Nombre de los radios. Con form: + attribute: sale del builder. |
| options | [{ value:, title:, description:, meta:, icon:, disabled: }] | [] | Opciones (también [título, valor, descripción]). O con slots: with_option(...) { ícono }. |
| label | String | nil | El <legend> del grupo (se ve como la etiqueta de Field). |
| selected | String | nil | Valor elegido al cargar (con form: sale del modelo). |
| columns | 1 | 2 | 3 | 4 | 1 | Columnas cuando el grupo tiene ancho (container queries); angosto siempre es una. |
| form: + attribute: | FormBuilder, Symbol | nil | Nombre, id, lo elegido y el error salen del modelo. |
| hint / error | String | nil | Ayuda y error del grupo (el del modelo se toma solo). |
| required / optional / disabled | Boolean | false | Asterisco, Opcional o el grupo entero deshabilitado (disabled por opción en options). |
| **attrs | Hash | {} | id, class, data… van al <fieldset>. |
Accesibilidad
- fieldset + legend nombran el grupo; cada tarjeta es la etiqueta de su radio nativo.
- Flechas para moverse entre opciones y Tab para salir del grupo, como cualquier radio.
- Descripción, dato, ayuda y error se leen con aria-describedby; aria-invalid con error.
- Anillo de foco en la tarjeta, solo con teclado. Con movimiento reducido el marco salta.