Contexto
Necesitaba un componente que se viera como un Button (con todas las variantes de estilo) pero que funcionara como un <a> semantico usando Next.js Link para client-side navigation.
Lo que aprendi
Base UI soporta un render prop que permite cambiar el elemento HTML subyacente. Combinado con Next.js Link, obtienes un link semantico con estilos de boton.
import Link from "next/link";
import { Button, type buttonVariants } from "@/components/ui/button";
import type { VariantProps } from "class-variance-authority";
type ButtonLinkProps = VariantProps<typeof buttonVariants> & {
href: string;
className?: string;
children: React.ReactNode;
};
export function ButtonLink({
href,
variant,
size,
className,
children,
}: ButtonLinkProps) {
return (
<Button
variant={variant}
size={size}
className={className}
render={<Link href={href} />}
>
{children}
</Button>
);
}
Uso
<ButtonLink href="/projects" variant="outline" size="sm">
Ver proyectos
</ButtonLink>
Que genera en el DOM
<a href="/projects" class="inline-flex items-center ... border border-input ...">
Ver proyectos
</a>
Es un <a> real (semantico, accesible, click-derecho funciona) pero con los estilos del Button.
Por que no <Link className={buttonVariants(...)}> directamente
Funciona, pero:
- Pierdes los focus styles y disabled states de Base UI
- Tienes que importar
buttonVariantsycnen cada uso - No tienes el
data-slotattribute que Base UI agrega
Por que no asChild (patron de Radix)
Base UI usa render prop en vez de asChild. El concepto es similar pero la implementacion es diferente:
// Radix style (asChild)
<Button asChild><Link href="/path">Text</Link></Button>
// Base UI style (render prop)
<Button render={<Link href="/path" />}>Text</Button>
El render prop es mas explicito sobre que elemento se va a renderizar.