DEV Community

Cover image for Polymorphic Button or Link Component with Dynamic Tailwind
Daniel Agha Babaei
Daniel Agha Babaei

Posted on • Updated on

Polymorphic Button or Link Component with Dynamic Tailwind

Before I explain polymorphic buttons or links, let me describe what led me to write this article. After finishing an article about Polymorphic Link/Button Components, I thought it would be a good idea to use it to better manage styles.
If you’re wondering, what was that article, It was this article: Polymorphic Link/Button Components in React & TypeScript.

I’m suggesting to first read it.

Let’s imagine we have 3 styles and these styles want to be used as a Link and a Button. If we want to do it, we should to create 2 components (Link and Button) and after that for each of them we should create props and class etc.

At this moment, we understood what happened and what’s going on. Right now, the question is this: How could we manage better? Let’s go.

Create a File

Firstly, We should create a component, its name is buttonOrlink.tsx.

Create component

enum ButtonOrLinkStyleModels {
  "model-1",
  "model-2",
  "model-3",
}

type ButtonOrLinkProps = (
  | (React.ButtonHTMLAttributes<HTMLButtonElement> & { as: "button" })
  | (React.AnchorHTMLAttributes<HTMLAnchorElement> & { as: "a" })
) & { styleModel: keyof typeof ButtonOrLinkStyleModels };

const ButtonOrLink = (props: ButtonOrLinkProps) => {
  const classNameProps = props.className ? props.className : "";
  const className = `${classNameProps} ${props.styleModel}`;
  return props.as === "a" ? (
    <a {...props} className={className} />
  ) : (
    <button {...props} className={className} />
  );
};
export default ButtonOrLink;

Enter fullscreen mode Exit fullscreen mode

If you’ve seen the article, the most of the things it same, but I just add only add 2 things:

  1. Enum
  2. Props styleModel

Let me first explain what Enum is on that component. As I said before we have 3 styles and for each of them it’s different.
We don’t know the styles, it could be something easy for us or hard, cause sometime we must use many classes on that button or link. It would be long I mean hover, focus, disabled etc.

For better manage styles and be short we go to the global styles and on there we write our classes for each of them.

Global Style

@tailwind base;
@tailwind components;
@tailwind utilities;

body {
  background-image: linear-gradient(135deg, #fdfcfb 0%, #e2d1c3 100%);
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
}

@layer components {
  .default {
    @apply flex px-0 py-2 items-center justify-center
     gap-[10px] border-[1.5px] border-white  
     shadow-lg text-xs text-white;
  }
  .model-1 {
    @apply default bg-blue-500 hover:text-blue-500
     hover:bg-white hover:border-blue-500
       w-[60px] h-[20px] rounded-2xl;
  }
  .model-2 {
    @apply default bg-gray-500 hover:text-gray-500
     hover:bg-white hover:border-gray-500
      w-[60px] h-[25px] rounded-lg;
  }
  .model-3 {
    @apply default bg-black hover:text-black
     hover:bg-white hover:border-black
     w-[70px] h-[70px] rounded-full;
  }
}

Enter fullscreen mode Exit fullscreen mode

In this file, what did I do ? I wrote we have a default style, and after that I said we have 3 model style (.model-1, .model-2, .model-3) and for all of them said have a default class and special style.

Tailwind.config.ts

You have to add these class in safelist in the tailwind.config.ts

import type { Config } from 'tailwindcss'

const config: Config = {
  content: [
    './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
    './src/components/**/*.{js,ts,jsx,tsx,mdx}',
    './src/app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {
    },
  },
  plugins: [],
  safelist:['model-1','model-2','model-3']
}
export default config
Enter fullscreen mode Exit fullscreen mode

Props styleModel

This prop styleModel handles which of the styles we want to add to that component and second prop as determine whether our component is a Link or Button

I created two components to better help you understand.

Links

import ButtonOrLink from "./buttonOrlink";

function Links() {
  return (
    <>
      <ButtonOrLink
        href="https://www.linkedin.com/in/daniel-aghababaei/"
        styleModel="model-1"
        as="a"
        target="_blank"
      >
        Linkedin
      </ButtonOrLink>
      <ButtonOrLink
        href="https://github.com/DanielDevel"
        target="_blank"
        styleModel="model-2"
        as="a"
      >
        Github
      </ButtonOrLink>
      <ButtonOrLink
        href="https://twitter.com/danieloaei"
        target="_blank"
        styleModel="model-3"
        as="a"
      >
        Twitter(X)
      </ButtonOrLink>
    </>
  );
}

export default Links;
Enter fullscreen mode Exit fullscreen mode

Buttons

"use client";
import ButtonOrLink from "./buttonOrlink";
import { useRouter } from "next/navigation";

function Buttons() {
  const router = useRouter();
  return (
    <>
      <ButtonOrLink
        onClick={()=> router.push("https://www.linkedin.com/in/daniel-aghababaei")}
        styleModel="model-1"
        as="button"
        type="button"
      >
        Linkedin
      </ButtonOrLink>
      <ButtonOrLink
        onClick={()=> router.push("https://github.com/danielaei")}
        type="button"
        styleModel="model-2"
        as="button"
      >
        Github
      </ButtonOrLink>
      <ButtonOrLink
        onClick={()=> router.push("https://twitter.com/danieloaei")}
        type="button"
        styleModel="model-3"
        as="button"
      >
        Twitter(X)
      </ButtonOrLink>
    </>
  );
}

export default Buttons;
Enter fullscreen mode Exit fullscreen mode

At the Conclusion it will look like this and also If you have issues, You can see on my GitHub.

Image description

Top comments (0)