Reusable Button component. Build Once, Use Everywhere.

# Building a Reusable Button Component in React
Creating a reusable button component in React is a fundamental task for any developer aiming to streamline UI development. A well-designed button component can be used across multiple projects, saving time and ensuring consistency.
In this blog, we'll build a flexible React button component that supports different variants, sizes, and element types (button or anchor). I'll explain the code, highlight its reusability, and provide ready to use code snippet.
# Why is it important?
Buttons are a core part of any user interface, appearing in forms, navigation bars, links, and more. A reusable button component allows developers to:
-
Reduce repetitive code by centralizing button logic.
-
Maintain consistent styling across the application.
-
Easily customize buttons with props for variants, sizes, and other functionality.
-
Support both
<button>and<a>elements for flexibility in navigation or actions.
[!note] We use Tailwind version 4
# Key Features + Code Snippet
-
Variants: Easily switch between primary, secondary, outline, and link stylings.
-
Sizes: Choose between large (lg) and medium (md) out of the box. Expand easily to support more.
-
Polymorphic: Render as either a standard
<button>for actions or an<a>for links, with proper external link handling. -
TypeScript Safety: All props are type-checked, and required fields (e.g., href for
<a>) are enforced. -
Customizable: Add more variants, sizes, or class overrides as your design system evolves.
# Code Snippet
import type { ButtonHTMLAttributes, AnchorHTMLAttributes, ReactNode, FC } from "react"
import { cn } from "@/shared/utils"
const buttonVariants = {
primary: "text-gray-50 bg-cyan-700 hover:bg-cyan-800",
secondary: "text-gray-50 bg-blue-700 hover:bg-blue-800",
outline: "text-gray-50 border border-current hover:bg-gray-100 hover:text-gray-900",
// learn more about tailwind groups: https://tailwindcss.com/docs/hover-focus-and-other-states#differentiating-nested-groups
link: "group/link text-gray-100 ",
}
const buttonSizes = {
lg: "px-4 py-3 rounded-none text-base ",
md: "px-3 py-2 rounded-none text-sm ",
}
type CommonProps = {
variant?: keyof typeof buttonVariants;
size?: keyof typeof buttonSizes;
classes?: string;
children: ReactNode;
};
type ButtonProps = {
element?: "button";
} & Omit<ButtonHTMLAttributes<HTMLButtonElement>, "className"> & CommonProps;
type AnchorProps = {
element: "a";
href: string; // <-- required when using 'a'
isExternal?: boolean;
} & Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "className"> & CommonProps;
type Props = ButtonProps | AnchorProps;
const ContentWrapper = ({ children }: { children: ReactNode }) => (
<span className="relative inline-block group-hover/link:underline group-hover/link:underline-offset-4">
{children}
</span>
);
const Button: FC<Props> = ({ variant = "primary", size = "lg", classes, element, children, ...rest }) => {
const className = cn(
"relative max-h-max leading-[0] inline-flex items-center font-semibold text-nowrap transition",
buttonSizes[size],
buttonVariants[variant],
classes,
)
if (element === "a") {
const { isExternal, ...anchorProps } = rest as AnchorProps
return (
<a
{...anchorProps}
className={className}
target={isExternal ? "_blank" : undefined}
rel={isExternal ? "noopener noreferrer" : undefined}
>
<ContentWrapper>
{children}
</ContentWrapper>
</a>
)
}
// Default: button
const buttonProps = rest as ButtonProps
return (
<button {...buttonProps} type="button" className={className}>
<ContentWrapper>
{children}
</ContentWrapper>
</button>
)
}
export default Button
Check out my YouTube video where I explain its functionality
# Code Explanation
Let's break down the key parts of the code to understand how it achieves reusability and flexibility.
-
The
buttonVariantsobject defines three variants (you can add more):primary,secondary, andtertiary, each with specific classes. This object contains styles related tocolors,shadow,hover/focuseffects. -
The
buttonSizesobject defines two sizes (you can add more):lg,md. It contains styles related to dimensions and spacingfont-size,height,width,padding,border-radiusetc. -
ContentWrapper is a helper component designed to wrap the visible content (children) inside reusable
Button, providing consistent styling for hover underline effects—especially important for the link variant.
# Summary
Here we build a truly reusable Button component using Tailwind. You only need to style once, and then reuse everywhere in your app. The code is type-safe, simple to drop in, and easy to extend when design needs change.