
Hay muchos bloques de interfaz de usuario en Tailwind Plus que necesitan JavaScript para ser realmente útiles, como diálogos, menús desplegables, paletas de comandos y más. Y a menos que seas usuario de React o Vue, usar esos bloques de interfaz de usuario siempre ha significado escribir todo ese JavaScript complicado por ti mismo.
Pues hoy eso finalmente cambia: cada bloque de interfaz de usuario en Tailwind Plus ahora es completamente funcional, accesible e interactivo, incluidos los ejemplos de HTML puro.
Ahora puedes usar cualquier menú desplegable, paleta de comandos, diálogo, panel lateral y más en cualquier proyecto en el que estés trabajando, sin necesidad de un framework de JavaScript.
Sin necesidad de framework
Para lograr esto, creamos @tailwindplus/elements, una biblioteca que lanzamos exclusivamente para clientes de Tailwind Plus.
Elements es una colección de elementos personalizados headless que encapsulan todo el comportamiento complejo necesario para construir interfaces de usuario interactivas y personalizadas usando solo HTML, y se pueden diseñar de la manera que desees utilizando clases de utilidad o CSS personalizado.
En lugar de estar acoplados a un framework de JavaScript específico, estos elementos personalizados funcionan en cualquier lugar donde puedas usar una etiqueta <script>:
<script src="https://cdn.jsdelivr.net/npm/@tailwindplus/elements@1" type="module"></script>Así se ve construir un menú desplegable personalizado con Elements:
<el-dropdown class="relative inline-block text-left"> <button class="inline-flex w-full justify-center gap-x-1.5 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-xs ring-1 ring-gray-300 ring-inset hover:bg-gray-50"> Opciones <svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="-mr-1 size-5 text-gray-400"> <path d="M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" fill-rule="evenodd" /> </svg> </button> <el-menu anchor="bottom end" popover class="w-56 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black/5 transition transition-discrete [--anchor-gap:--spacing(2)] focus:outline-hidden data-closed:scale-95 data-closed:transform data-closed:opacity-0 data-enter:duration-100 data-enter:ease-out data-leave:duration-75 data-leave:ease-in"> <div class="py-1"> <a href="#" class="block px-4 py-2 text-sm text-gray-700 focus:bg-gray-100 focus:text-gray-900 focus:outline-hidden">Configuración de la cuenta</a> <a href="#" class="block px-4 py-2 text-sm text-gray-700 focus:bg-gray-100 focus:text-gray-900 focus:outline-hidden">Soporte</a> <a href="#" class="block px-4 py-2 text-sm text-gray-700 focus:bg-gray-100 focus:text-gray-900 focus:outline-hidden">Licencia</a> <form action="#" method="POST"> <button type="submit" class="block w-full px-4 py-2 text-left text-sm text-gray-700 focus:bg-gray-100 focus:text-gray-900 focus:outline-hidden">Cerrar sesión</button> </form> </div> </el-menu></el-dropdown>Y aquí tienes cómo se ve construir un menú de selección personalizado:
<label for="select" class="block text-sm/6 font-medium text-gray-900">Asignado a</label><el-select id="select" name="selected" value="4" class="mt-2 block"> <button type="button" class="grid w-full cursor-default grid-cols-1 ..."> <el-selectedcontent></el-selectedcontent> <svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" class="col-start-1 row-start-1 ..."> <!-- ... --> </svg> </button> <el-options anchor="bottom start" popover class="max-h-60 w-(--button-width) [--anchor-gap:--spacing(1)] ..."> <el-option value="1" class="group/option relative block focus:bg-indigo-600 ..."> <div class="flex items-center"> <span aria-hidden="true" class="inline-block size-2 shrink-0 ..."></span> <span class="ml-3 block group-aria-selected/option:font-semibold ..."> Wade Cooper <span class="sr-only"> está en línea</span> </span> </div> <span class="group-not-aria-selected/option:hidden group-focus/option:text-white in-[el-selectedcontent]:hidden ..."> <svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="size-5"> <!-- ... --> </svg> </span> </el-option> <!-- ... --> </el-options></el-select>¿Ves esos elementos HTML personalizados como <el-select> y <el-options>? Esos son la fórmula secreta que hace que todo funcione, incluyendo la gestión automática de atributos ARIA, el manejo del foco, el soporte para teclado y más.
Incluso puedes construir algo tan sofisticado como una paleta de comandos personalizada con Elements, sin tener que escribir nada de tu propio JavaScript:
<button command="show-modal" commandfor="dialog" class="rounded-md bg-white/80 px-2.5 py-1.5 text-sm font-semibold text-gray-900 hover:bg-white/90"> Abrir paleta de comandos</button><el-dialog> <dialog id="dialog" class="backdrop:bg-transparent"> <el-dialog-backdrop class="fixed inset-0 bg-gray-500/25 transition-opacity data-closed:opacity-0 data-enter:duration-300 data-enter:ease-out data-leave:duration-200 data-leave:ease-in"></div> <div tabindex="0" class="fixed inset-0 w-screen overflow-y-auto p-4 focus:outline-none sm:p-6 md:p-20"> <el-dialog-panel class="mx-auto block max-w-3xl transform overflow-hidden rounded-xl bg-white shadow-2xl ring-1 ring-black/5 transition-all data-closed:scale-95 data-closed:opacity-0 data-enter:duration-300 data-enter:ease-out data-leave:duration-200 data-leave:ease-in"> <el-command-palette class="divide-y divide-gray-100"> <div class="grid grid-cols-1"> <input type="text" autofocus placeholder="Buscar..." class="col-start-1 row-start-1 h-12 w-full pr-4 pl-11 text-base text-gray-900 outline-hidden placeholder:text-gray-400 sm:text-sm" /> <svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="pointer-events-none col-start-1 row-start-1 ml-4 size-5 self-center text-gray-400"> <path d="M9 3.5a5.5 5.5 0 1 0 0 11 5.5 5.5 0 0 0 0-11ZM2 9a7 7 0 1 1 12.452 4.391l3.328 3.329a.75.75 0 1 1-1.06 1.06l-3.329-3.328A7 7 0 0 1 2 9Z" clip-rule="evenodd" fill-rule="evenodd" /> </svg> </div> <div class="flex transform-gpu divide-x divide-gray-100"> <div class="max-h-96 min-w-0 flex-auto scroll-py-4 overflow-y-auto px-6 py-4"> <el-command-list class="-mx-2 block text-sm text-gray-700"> <el-defaults> <h2 class="mx-2 mt-2 mb-4 text-xs font-semibold text-gray-500">Búsquedas recientes</h2> <div class="text-sm text-gray-700"> <a id="person-suggestion-6" href="#" class="group flex cursor-default items-center rounded-md p-2 select-none focus:outline-hidden aria-selected:bg-gray-100 aria-selected:text-gray-900"> <img src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="" class="size-6 flex-none rounded-full" /> <span class="ml-3 flex-auto truncate">Tom Cook</span> <svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="ml-3 hidden size-5 flex-none text-gray-400 group-aria-selected:block"> <path d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" fill-rule="evenodd" /> </svg> </a> <!-- ... --> </div> </el-defaults> <el-command-group hidden class="sm:h-96"> <a id="person-1" href="#" hidden class="group flex cursor-default items-center rounded-md p-2 select-none focus:outline-hidden aria-selected:bg-gray-100 aria-selected:text-gray-900"> <img src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="" class="size-6 flex-none rounded-full" /> <span class="ml-3 flex-auto truncate">Leslie Alexander</span> <svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="ml-3 hidden size-5 flex-none text-gray-400 group-aria-selected:block"> <path d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" fill-rule="evenodd" /> </svg> </a> <!-- ... --> </el-command-group> </el-command-list> <el-no-results hidden class="block px-6 py-14 text-center text-sm sm:px-14"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true" class="mx-auto size-6 text-gray-400"> <path d="M15 19.128a9.38 9.38 0 0 0 2.625.372 9.337 9.337 0 0 0 4.121-.952 4.125 4.125 0 0 0-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 0 1 8.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0 1 11.964-3.07M12 6.375a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0Zm8.25 2.25a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z" stroke-linecap="round" stroke-linejoin="round" /> </svg> <p class="mt-4 font-semibold text-gray-900">No se encontraron personas</p> <p class="mt-2 text-gray-500">No pudimos encontrar nada con ese término. Por favor, inténtalo de nuevo.</p> </el-no-results> </div> <el-command-preview for="person-1" hidden class="h-96 w-1/2 flex-none flex-col divide-y divide-gray-100 overflow-y-auto sm:flex"> <div class="flex-none p-6 text-center"> <img src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="" class="mx-auto size-16 rounded-full" /> <h2 class="mt-3 font-semibold text-gray-900">Leslie Alexander</h2> <p class="text-sm/6 text-gray-500">Cofundador / CEO</p> </div> <div class="flex flex-auto flex-col justify-between p-6"> <dl class="grid grid-cols-1 gap-x-6 gap-y-3 text-sm text-gray-700"> <dt class="col-end-1 font-semibold text-gray-900">Teléfono</dt> <dd>1-493-747-9031</dd> <dt class="col-end-1 font-semibold text-gray-900">URL</dt> <dd class="truncate"><a href="https://example.com" class="text-indigo-600 underline">https://example.com</a></dd> <dt class="col-end-1 font-semibold text-gray-900">Correo electrónico</dt> <dd class="truncate"><a href="mailto:[email protected]" class="text-indigo-600 underline">[email protected]</a></dd> </dl> <button type="button" class="mt-6 w-full rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-indigo-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Enviar mensaje</button> </div> </el-command-preview> <!-- ... --> </div> </el-command-palette> </el-dialog-panel> </div> </dialog></el-dialog>Para dar soporte a todos los bloques de interfaz de usuario en Tailwind Plus, incluimos las siguientes primitivas en este primer lanzamiento de Elements:
- Autocomplete — para construir elementos como Comboboxes personalizados.
- Command palette — para construir Paletas de comandos personalizadas.
- Dialog — para construir Diálogos modales personalizados, Paneles laterales y más.
- Disclosure — para construir secciones de Preguntas frecuentes (FAQ) colapsables, menús móviles en Barras de navegación y más.
- Dropdown menu — para construir Menús desplegables personalizados, por supuesto.
- Popover — para construir Menús flotantes personalizados y más.
- Select — para construir Menús de selección personalizados.
- Tabs — para construir pestañas personalizadas, como las que usamos en Áreas de texto y Vistas generales de productos personalizadas.
Si eres cliente de Tailwind Plus, dirígete a la nueva documentación de Elements para obtener más información sobre cómo funciona todo y ver algunos ejemplos.
Aprovechando la web moderna
Nos apoyamos en muchas características modernas de la plataforma para mantener a Elements lo más ligero y nativo posible.
- Elementos personalizados como una abstracción de componentes multiplataforma.
- El atributo
popoverpara gestionar superposiciones y ventanas emergentes con renderizado automático en la capa superior (top-layer) junto conbeforetogglepara controlar las transiciones. - Elementos
<dialog>nativos para captura de foco y renderizado en la capa superior. - Comandos invocadores para gestionar de forma declarativa elementos interactivos, como alternar una divulgación (disclosure) personalizada.
ElementInternalspara hacer que nuestros controles de formulario personalizados funcionen como controles de formulario nativos.
También incluimos los polyfills necesarios para estas características para asegurarnos de que Elements funcione en todos los mismos navegadores compatibles con Tailwind CSS v4.0. Esto significa que Elements solo se reducirá de tamaño a medida que estas características modernas de la plataforma estén más disponibles.
Componentes que funcionan en todas partes
Dado que el HTML es el mínimo común denominador entre todos los frameworks web, Elements hace posible que todos los bloques de interfaz de usuario de solo HTML en Tailwind Plus funcionen literalmente en cualquier lugar.
Aquí tienes uno de nuestros ejemplos de Combobox conectado con enlace bidireccional (two-way binding) en Svelte:
<script> let input = $state(""); function handleSubmit() { alert(`Selected: ${input}`); }</script><form onsubmit={handleSubmit}> <label for="autocomplete" class="block text-sm/6 font-medium text-gray-900">Asignado a</label> <el-autocomplete class="relative mt-2 block"> <input bind:value={input} id="autocomplete" type="text" class="block w-full rounded-md ..." /> <button type="button" class="absolute inset-y-0 right-0 flex ..."> <svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="size-5 text-gray-400"> <path d="M5.22 8.22a.75.75 ..." clip-rule="evenodd" fill-rule="evenodd" /> </svg> </button> <el-options anchor="bottom end" popover class="max-h-60 w-(--input-width) [--anchor-gap:--spacing(1)] ..."> <el-option value="Leslie Alexander" class="block truncate aria-selected:bg-indigo-600 aria-selected:text-white ...">Leslie Alexander</el-option> </el-options> </el-autocomplete> <button type="submit">Submit</button></form>O aquí tienes un select personalizado en Rails que se incluye en los envíos de formularios, al igual que un control de formulario nativo:
class OrdersController < ApplicationController def new @cars = Car.all @selected_car = @cars.first end def create car = Car.find(params[:car_id]) flash[:notice] = "Selected car: #{car.name}" redirect_to root_path endend<%= form_with do |form| %> <%= form.label :car_id, "Seleccionar modelo:" %> <el-select name="car_id" id="car_id" value="<%= @selected_car.id %>"> <button type="button" class="grid w-full cursor-default grid-cols-1 ..."> <el-selectedcontent class="col-start-1 row-start-1 truncate pr-6"> <%= @selected_car.name %> </el-selectedcontent> <svg viewBox="0 0 16 16" aria-hidden="true" class="col-start-1 row-start-1 size-5 ..."> <path d="M5.22 10.22a.75.75 ..." clip-rule="evenodd" fill-rule="evenodd" /> </svg> </button> <el-options anchor="bottom end" popover=""> <% @cars.each do |car| %> <el-option value="<%= car.id %>"> <span class="block truncate font-normal group-aria-selected/option:font-semibold"> <%= car.name %> </span> <span class="flex group-not-aria-selected/option:hidden group-focus/option:text-white in-[el-selectedcontent]:hidden ..."> <svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="size-5"> <path d="M16.704 4.153a.75.75 0 ..." clip-rule="evenodd" fill-rule="evenodd" /> </svg> </span> </el-option> <% end %> </el-options> </el-select> <%= form.submit "Realizar pedido" %><% end %>Incluso puedes usar Elements en React en lugar de usar una biblioteca exclusiva para React como Headless UI o React Aria si lo deseas:
import Link from "next/link";export function Menu() { return ( <el-dropdown className="relative inline-block text-left"> <button className="inline-flex w-full justify-center ..."> Menú <svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" className="-mr-1 size-5 text-gray-400"> <path d="M5.22 8.22a.75.75 ..." clip-rule="evenodd" fill-rule="evenodd" /> </svg> </button> <el-menu anchor="bottom end" popover className="transition transition-discrete [--anchor-gap:--spacing(2)] focus:outline-hidden data-closed:scale-95 ..."> <div className="py-1"> <Link href="/" className="block px-4 py-2 focus:bg-gray-100 focus:text-gray-900 focus:outline-hidden ...">Inicio</Link> <Link href="/about" className="block px-4 py-2 focus:bg-gray-100 focus:text-gray-900 focus:outline-hidden ...">Acerca de</Link> <Link href="/faq" className="block px-4 py-2 focus:bg-gray-100 focus:text-gray-900 focus:outline-hidden ...">Preguntas frecuentes</Link> </div> </el-menu> </el-dropdown> );}Pruébalo hoy mismo
Todos los bloques de interfaz de usuario actualizados y la nueva biblioteca Elements ya están disponibles para todos los clientes de Tailwind Plus.
Echa un vistazo a las categorías de bloques de interfaz de usuario como Menús desplegables y Paletas de comandos para probar estos ejemplos interactivos actualizados por ti mismo, y explora la nueva documentación de Elements para aprender cómo funciona todo y cómo personalizar las cosas para tus proyectos.
¡Estamos ansiosos por ver lo que construirás con esto!