Saltar al contenido

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 — default lleva la acción principal; secondary y outline para todo lo que la acompaƱa.
  • Barras de herramientas y acciones de icono — los tamaƱos icon producen botones cuadrados; ButtonGroup los alinea en filas segmentadas.
  • Confirmaciones destructivas — destructive para eliminar y quitar, normalmente detrĆ”s de un diĆ”logo.

Instalación

pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/button.json
npx shadcn@latest add https://neobrutalism.com/r/base/button.json
yarn dlx shadcn@latest add https://neobrutalism.com/r/base/button.json
bunx --bun shadcn@latest add https://neobrutalism.com/r/base/button.json

Instala las siguientes dependencias:

pnpm add @base-ui/react
npm install @base-ui/react
yarn add @base-ui/react
bun add @base-ui/react

Copia y pega el siguiente código en tu proyecto.

components/ui/button.tsx
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.

globals.css
@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() {
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:

TeclaAcción
Tab / Shift + TabLleva el foco al botón o fuera de él
Space / EnterActiva 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.

PropTypeDefault
variant"default" | "outline" | "ghost" | "destructive" | "secondary" | "link""default"
size"default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg""default"