Botón
Un botón en el que puedes hacer clic para enviar formularios, abrir barras de herramientas y confirmar acciones irreversibles, con bordes gruesos y una sombra que se acentúa al hacer clic.
import { ArrowUpIcon } from "lucide-react"
import { Button } from "@/components/ui/button"El botón desencadena una acción: enviar, confirmar, eliminar. EstÔ construido sobre el componente Base UI Button y sigue la receta neobrutalista: bordes gruesos, una sombra desplazada dura y una pulsación activa que hunde el botón en ella.
Ćsalo cuando:
- EnvĆos de formulario y CTA principales ā
defaultlleva la acción principal;secondaryyoutlinepara todo lo que la acompaƱa. - Barras de herramientas y acciones de icono ā los tamaƱos
iconproducen botones cuadrados;ButtonGrouplos alinea en filas segmentadas. - Confirmaciones destructivas ā
destructivepara eliminar y quitar, normalmente detrƔs de un diƔlogo.
Instalación#
pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/button.jsonnpx shadcn@latest add https://neobrutalism.com/r/base/button.jsonyarn dlx shadcn@latest add https://neobrutalism.com/r/base/button.jsonbunx --bun shadcn@latest add https://neobrutalism.com/r/base/button.json
Instala las siguientes dependencias:
pnpm add @base-ui/reactnpm install @base-ui/reactyarn add @base-ui/reactbun add @base-ui/react
Copia y pega el siguiente código en tu proyecto.
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
cn(
"group/button font-head font-medium inline-flex cursor-pointer items-center justify-center gap-2 rounded whitespace-nowrap select-none transition-all duration-200",
"disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-60",
"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary aria-invalid:border-destructive",
// Icons keep their own size; we only set a default when none is given so
// Neobrutalism's h-4/size-4 icons aren't overridden.
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
),
{
variants: {
variant: {
default:
"border-2 border-black bg-primary text-primary-foreground shadow-md transition duration-200 hover:-translate-x-0.5 hover:-translate-y-0.5 hover:bg-primary-hover hover:shadow-lg active:translate-x-1 active:translate-y-1 active:shadow-none",
secondary:
"border-2 border-black bg-secondary text-secondary-foreground shadow-md transition duration-200 hover:-translate-x-0.5 hover:-translate-y-0.5 hover:bg-secondary-hover hover:shadow-lg active:translate-x-1 active:translate-y-1 active:shadow-none",
destructive:
"border-2 border-black bg-destructive text-destructive-foreground shadow-md transition duration-200 hover:-translate-x-0.5 hover:-translate-y-0.5 hover:bg-destructive/90 hover:shadow-lg active:translate-x-1 active:translate-y-1 active:shadow-none",
outline:
"border-2 bg-transparent shadow-md transition duration-200 hover:-translate-x-0.5 hover:-translate-y-0.5 hover:shadow-lg active:translate-x-1 active:translate-y-1 active:shadow-none",
ghost: "bg-transparent hover:bg-accent",
link: "bg-transparent hover:underline",
},
size: {
default: "px-4 py-1.5 text-base",
xs: "px-2 py-0.5 text-xs",
sm: "px-3 py-1 text-sm",
lg: "px-6 py-2 text-base lg:px-8 lg:py-3 lg:text-lg",
icon: "p-2",
"icon-xs": "p-1",
"icon-sm": "p-1.5",
"icon-lg": "p-3",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Button({
className,
variant = "default",
size = "default",
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot.Root : "button"
return (
<Comp
data-slot="button"
data-variant={variant}
data-size={size}
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Button, buttonVariants }
Actualiza las rutas de importación para que coincidan con la estructura de tu proyecto.
Uso#
import { Button } from "@/components/ui/button"<Button variant="outline">Button</Button>Cursor#
Tailwind v4 cambió de cursor: pointer a cursor: default para el componente de botón.
Si quieres conservar el comportamiento cursor: pointer, añade el siguiente código a tu archivo CSS:
También puedes activarlo durante la configuración del proyecto con npx shadcn@latest init --pointer.
@layer base {
button:not(:disabled),
[role="button"]:not(:disabled) {
cursor: pointer;
}
}Ejemplos#
TamaƱo#
Usa la prop size para cambiar el tamaño del botón.
import { ArrowUpRightIcon } from "lucide-react"
import { Button } from "@/components/ui/button"Default#
import { Button } from "@/components/ui/button"
export function ButtonDefault() {Outline#
import { Button } from "@/components/ui/button"
export function ButtonOutline() {Secondary#
import { Button } from "@/components/ui/button"
export function ButtonSecondary() {Ghost#
import { Button } from "@/components/ui/button"
export function ButtonGhost() {Destructive#
import { Button } from "@/components/ui/button"
export function ButtonDestructive() {Link#
import { Button } from "@/components/ui/button"
export function ButtonLink() {Icon#
import { CircleFadingArrowUpIcon } from "lucide-react"
import { Button } from "@/components/ui/button"Con icono#
Recuerda aƱadir el atributo data-icon="inline-start" o data-icon="inline-end" al icono para que el espaciado sea correcto.
import { IconGitBranch, IconGitFork } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"Redondeado#
Usa la clase rounded-full para redondear el botón.
import { ArrowUpIcon } from "lucide-react"
import { Button } from "@/components/ui/button"Spinner#
Renderiza un componente <Spinner /> dentro del botón para mostrar un estado de carga. Recuerda añadir el atributo data-icon="inline-start" o data-icon="inline-end" al spinner para que el espaciado sea correcto.
import { Button } from "@/components/ui/button"
import { Spinner } from "@/components/ui/spinner"
Button Group#
Para crear un grupo de botones, usa el componente ButtonGroup. Consulta la documentación de Button Group para mÔs detalles.
"use client"
import * as React from "react"Como enlace#
Puedes usar el helper buttonVariants para que un enlace tenga el aspecto de un botón.
No uses <Button render={<a />} nativeButton={false} /> para los enlaces. El componente Button de Base UI siempre aplica role="button", lo que anula el rol semƔntico de enlace en los elementos <a>. Usa buttonVariants con una etiqueta <a> simple en su lugar.
import { buttonVariants } from "@/components/ui/button"
export function ButtonRender() {RTL#
Para activar la compatibilidad con RTL en Neobrutalism, consulta la guĆa de configuración de RTL.
"use client"
import { ArrowRightIcon, PlusIcon } from "lucide-react"Accesibilidad#
El botón sigue el patrón Button de WAI-ARIA. Por defecto renderiza un <button> nativo; cuando render lo sustituye por otro elemento, Base UI le conecta role="button", tabindex y la activación por teclado para que el sustituto se comporte como un botón real ante las tecnologĆas de asistencia (consulta la nota de la sección Como enlace, mĆ”s arriba, para el Ćŗnico caso en el que ese rol no corresponde).
Interacciones de teclado:
| Tecla | Acción |
|---|---|
Tab / Shift + Tab | Lleva el foco al botón o fuera de él |
Space / Enter | Activa el botón |
Referencia de API#
Button#
El componente Button es un envoltorio del elemento button que aƱade una variedad de estilos y funcionalidades.
| Prop | Type | Default |
|---|---|---|
variant | "default" | "outline" | "ghost" | "destructive" | "secondary" | "link" | "default" |
size | "default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | "default" |