Fundamentos

Íconos

Un solo set, un solo trazo y tamaños fijos.

Un solo set para toda la interfaz: 89 íconos en 6 grupos, dibujados en una rejilla de 24 con un trazo de 2, puntas y uniones redondas, sin rellenos, y del color del texto que los rodea (currentColor). Los pinta Ui::IconComponent; la geometría vive en app/components/ui/icons.rb y viene de Lucide (licencia ISC).

El set

Busca por el nombre en inglés (como en Lucide) o por lo que buscas en español: «tarjeta», «maestra», «eliminar». Un clic o Enter copia el nombre listo para Ruby.

Íconos

89 íconos

Flechas y navegación

Acciones

Estado y avisos

Agenda y personas

Comercio

Contenido y contacto

Tamaños y trazo

Cinco tamaños fijos. El ícono acompaña al texto de su tamaño: chico junto a metadatos, md en botones, menús y campos, lg en cuadritos y avisos, xl solo en marcas grandes (estados vacíos, la foto de perfil vacía). Nada de size-* sueltos ni size-[18px]: si ninguno cabe, el diseño es el que no cabe.

Tamaños

:xs — trazo —

:sm — trazo —

:md — trazo —

:lg — trazo —

:xl — trazo —

xs · con text-2xs

sm · mar 14 oct · 07:00

lg · en un cuadrito de 40px

TamañopxTrazo en pantallaVa conUsos hoy
:xs 12 1px text-2xs y text-3xs: pastillas, flechas de tendencia, migas 8
:sm 14 1.17px text-xs y text-compact: metadatos, errores de campo, botones chicos 36
:md (default) 16 1.33px text-sm: botones, menús, campos, cerrar 30
:lg 20 1.67px cuadritos de 32–40px, la campana, diálogos 6
:xl 24 2px marcas grandes: estado vacío, foto vacía 1

Por qué el trazo es 2

A 14px un trazo de 2 queda en ~1.17px: el mismo grosor que el asta de Inter a 13–14px, así que ícono y etiqueta pesan igual. Con 1.75 baja a ~1px, cae entre dos columnas de píxeles y en los tonos tenues (text-subtle) se ve gris y borroso, sobre todo en oscuro. Además la rejilla de Lucide está dibujada para 2: los puntos de la alerta o del pin salen del grosor. Un solo trazo para todo; por eso Ui::IconComponent no acepta stroke_width:.

1.75 contra 2, a 14px
1.75 Vinyasa Flow · Sala Luna
2 Vinyasa Flow · Sala Luna

Color

El ícono no tiene color propio: toma el del texto (currentColor). Se le pone una clase de texto solo cuando debe ir más tenue o llevar un estado.

  • text-subtle junto a metadatos (hora, lugar) y dentro de campos.
  • text-muted-foreground en menús y botones de ícono en reposo; text-foreground al pasar el cursor.
  • text-warning, text-destructive, text-success solo cuando el ícono lleva un estado, y siempre con texto al lado.
Color

Vinyasa Flow

07:00–08:00

Sala Luna

Editar

Duplicar

Eliminar

Faltan menos de 12 horas: el crédito no se devuelve.

Decorativo o con nombre

Casi siempre el ícono es decorativo: el texto de al lado o el aria-label del botón ya dicen qué es, y el lector de pantalla no debe leerlo dos veces. Sin label: sale con aria-hidden="true".

  • Ícono con texto (Editar, Reservar clase): decorativo.
  • Botón solo de ícono (cerrar, más acciones): el nombre va en el botón (aria-label="Cerrar aviso") y el ícono sigue decorativo.
  • El ícono es la información (una palomita que dice «reservada» sin texto, un estado en una celda): label:, y sale con role="img" y su aria-label.

Lo que pinta cada uno

# Ui::IconComponent.svg(:x, size: :sm)
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-3.5 shrink-0" data-icon="x" aria-hidden="true"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>

# Ui::IconComponent.svg(:check, size: :xs, label: "Reservada")
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-3 shrink-0" data-icon="check" role="img" aria-label="Reservada"><path d="M20 6 9 17l-5-5"/></svg>

Cómo se usa

En una plantilla, con render. En un método call o en un helper (Ruby, sin plantilla), con el atajo Ui::IconComponent.svg, que devuelve el HTML. Los nombres aceptan guion o guion bajo: :chevron_down y "chevron-down" son el mismo. Un nombre que no existe falla en desarrollo y en las pruebas, con sugerencia.

Plantilla (.html.erb) y Ruby

<%# Junto a un texto: decorativo, toma el color del texto. %>
<p class="flex items-center gap-1.5 text-xs text-muted-foreground">
  <%= render Ui::IconComponent.new(:clock, size: :sm, class: "text-subtle") %>
  07:00–08:00
</p>

<%# Botón solo de ícono: el nombre va en el botón. %>
<button type="button" aria-label="Cerrar aviso"><%= render Ui::IconComponent.new(:x, size: :sm) %></button>

# En un método call (Ruby):
def close_button
  tag.button(Ui::IconComponent.svg(:x, size: :sm), type: "button", aria: { label: "Cerrar aviso" })
end

Los componentes que muestran un ícono lo piden por nombre, del mismo set:

ComponenteParámetroEjemplo
Ui::AlertComponenticon: (o false para quitarlo)icon: :calendar
Ui::DropdownMenuComponenticon: de cada opciónm.with_item_link("Editar", href: …, icon: :pencil)
Ui::RadioCardsComponenticon: de cada opción{ value: "card", title: "Tarjeta", icon: :credit_card }
Ui::StatCardComponenticon:icon: :users

Los nombres viejos de esas APIs siguen funcionando como alias: :trash → :trash_2, :card → :credit_card, :check_in → :user_check, :dashboard → :layout_dashboard, :logout → :log_out, :error → :circle_alert, :success → :circle_check, :warning → :triangle_alert.

Lo que no es un ícono

Algunos gráficos se escriben aparte a propósito: se animan trazo a trazo, cambian de forma con un resorte o son datos. No van al set ni a Ui::IconComponent. Otros ni siquiera son SVG, son CSS: la media luna del catálogo y del estado vacío, el medidor de cupo, las barras de las gráficas, los spinners y el asa de la hoja. Esta lista sale de buscar <svg en los componentes y vistas del catálogo: si aparece uno sin explicación, casi seguro es un ícono pegado a mano.

Gráficos con vida
14 de 18 lugares
ArchivoGráficosPor qué no es un ícono
action_button_component.html.erb 2 El arco de carga gira y la palomita de éxito se dibuja.
input_component.html.erb 2 El ojo de la contraseña parpadea (el controlador redibuja el párpado) y la palomita de cada regla de fuerza se dibuja.
stat_card_component.html.erb 1 La línea de tendencia (sparkline) es un dato, no un ícono.
textarea_component.html.erb 1 El anillo del contador se llena conforme escribes.
checkbox_component.rb 1 La palomita se dibuja trazo a trazo al marcar (pathLength).

Agregar uno

  1. Búscalo en lucide.dev con el nombre en inglés. El set está tomado de lucide-static 1.48.0.
  2. Copia solo los hijos del <svg> (path, circle, rect…), sin stroke-width ni clases.
  3. Ponlo en su grupo de app/components/ui/icons.rb con su nombre de Lucide y palabras clave en español: lo que alguien escribiría para buscarlo.
  4. Corre bin/rails test test/components/ui/icon_component_test.rb: revisa que el nombre sea válido y que todo el set se pinte.
  5. Si Lucide no lo tiene, dibújalo en la misma rejilla de 24, con trazo de 2, sin rellenos y con puntas redondas, y dilo en un comentario.

Sin marcas. Los logos (WhatsApp, Instagram, Visa) tienen reglas y colores propios y no se dibujan con el trazo del set; si hacen falta, van como la imagen oficial de la marca.