DEV Community

Gabriel Linassi
Gabriel Linassi

Posted on

How to create a polymorphic Button with variants in NextJS + TailwindCSS + TS + RadixUI

This is a quick post just to share a nice way how to do it with full TypeScript support. It uses RadixUI Slot component for the polymorphism.

import React, { forwardRef } from 'react';
import { cnMerge } from 'common/cn-merge';
import { Slot } from '@radix-ui/react-slot';

const baseClasses = /*tw:*/ `inline-flex items-center justify-center gap-2 font-bold text-center rounded-md`;

const variantsLookup = {
  solid: /*tw:*/ `text-white bg-primary hover:shadow-button duration-100`,
  outline: /*tw:*/ `text-primary border border-blue-300 hover:bg-blue-600 hover:border-primary`,
  subtle: /*tw:*/ `text-primary hover:bg-blue-600`,
};

const sizesLookup = {
  base: /*tw:*/ `px-5 py-2.5`,
};

type ButtonProps = {
  children: React.ReactNode;
  variant?: 'outline' | 'solid' | 'subtle';
  size?: 'base';
  href?: string;
  className?: string;
  asChild?: boolean;
};

const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
  const { children, variant, size, asChild, className, ...rest } = props;

  const classes = cnMerge([
    baseClasses,
    variantsLookup[variant || 'solid'],
    sizesLookup[size || 'base'],
    className,
  ]);

  const Comp = asChild ? Slot : 'button';

  return (
    <Comp {...rest} className={classes} ref={ref}>
      {children}
    </Comp>
  );
});

export default Button;
Enter fullscreen mode Exit fullscreen mode

Usage:

<NextLink href="/extension" passHref>
  <Button asChild>
    <a>
      <ExtensionIcon />
      Install Chrome Extension
    </a>
  </Button>
</NextLink>

/* ---- */

<Button asChild>
  <a href="https://wwww.test.com" target="_blank" rel="noopener noreferrer">
    <ExtensionIcon />
    Install Chrome Extension
  </a>
</Button>

Enter fullscreen mode Exit fullscreen mode

Top comments (0)