Lexington has been awarded a grant from Astro, to celebrate. Get a 30% discount. Apply code LEXINGTON30 ( uppercase ) at checkout.

← Back to all tutorials

How to create a button component with variants and icons with Astro JS and Tailwind CSS

Bottom Navigation
Published and written on Oct 02 2024 by Michael Andreuzza

Hello everyone! Today I am going to kickstart tutorial for Astro JS. This tutorial is taking into account you already know how to use Astro JS, we are starting from the button itself

So what are Astro JS componet

An Astro button component is a reusable and customizable button used in Astro.js projects. It makes it easy to create buttons with different styles, sizes, and functions. Developers can define various button types (like primary, secondary, or outlined) and even include icons. This component helps keep the code clean and consistent, allowing for easier updates and a more unified look across the application.

Let’s start creating our button.

The first thing we need to do is create a new file under the /components/fundations/buttons folder. for example, and we are going to call it Button.astro

Let’s start writing the ## Props Destructuring and Initialization Section

The following code represents the props destructuring and initialization section of an Astro button component. This part of the code pulls out the properties from the Astro.props object, making it easier to access and use them in your component.

Breakdown:

  1. Destructuring:
    • The first line extracts values from Astro.props into individual variables (variant, size, onlyIconSize, gap, className). This makes the values easier to use in your component.
const {
  variant,
  size,
  onlyIconSize,
  gap,
  class: className,
  ...rest
} = Astro.props;
  1. Default Classes:
    • The defaultClass array defines a set of default Tailwind CSS classes for styling the button. This is the baseline style applied unless overridden by other variants.
// Default button
const defaultClass = [
  "text-white",
  "bg-neutral-900",
  "hover:bg-neutral-800",
  "focus:ring-neutral-500/50",
];
// More variants go here
  1. Size and Gap Classes:
    • The following arrays (xs, gapXS, etc.) define classes for different button sizes and gaps. This allows the component to adjust its styling based on the specified size and spacing.
// More variants go here
// Size
const xs = ["h-8", "px-4", "py-2", "text-xs", "font-medium", "rounded-md"];
// More sizes go here
// Gap
const gapXS = ["gap-2"];
  1. Additional Classes:
    • The additionalClasses variable collects any extra classes passed through the class prop, allowing for further customization of the button’s appearance.
const additionalClasses = className ? className.split(" ") : [];

This section effectively sets up the component’s styling rules based on the props passed to it.

---
const {
  variant,
  size,
  onlyIconSize,
  gap,
  class: className,
  ...rest
} = Astro.props;
// Default button
const defaultClass = [
  "text-white",
  "bg-neutral-900",
  "hover:bg-neutral-800",
  "focus:ring-neutral-500/50",
];
// More variants go here
// Size
const xs = ["h-8", "px-4", "py-2", "text-xs", "font-medium", "rounded-md"];
// More sizes go here
// Gap
const gapXS = ["gap-2"];
// More gaps go here
const additionalClasses = className ? className.split(" ") : [];
---

Class Binding with class:list

The <button> element utilizes class:list to conditionally apply Tailwind CSS classes based on the props passed to the component. This allows for dynamic styling based on different states or props.

Key Components of the Class List:

  1. Base Flex Classes:

    • "flex": Sets the button to use Flexbox layout.
    • "items-center": Vertically centers the items within the button.
    • "justify-center": Horizontally centers the items within the button.
    • "transition-all": Applies transition effects to all properties.
    • "duration-300": Sets the transition duration to 300ms.
    • "focus:ring-2": Adds a ring effect on focus.
    • "focus:outline-none": Removes the default outline when focused.
  2. Variant Classes:

    • Each variant (e.g., accent, default, alternative, etc.) has associated classes that are applied conditionally. For example:
      • variant === "accent" && accentClass: If the variant is "accent", accentClass is applied.
      • This structure allows the button to have different styles based on its purpose or state.
  3. Size Classes:

    • Similar to variants, size classes adjust the button’s appearance based on the size prop. For instance:
      • size === "xs" && xs: Applies the xs size classes if the size is "xs".
  4. Gap Classes:

    • Gap classes determine spacing between elements within the button, adjusting based on the gap prop.
  5. Additional Classes:

    • The spread operator ...additionalClasses allows for any extra classes passed through the class prop to be included, enabling further customization.

Content Slots

  • The <slot> elements within the button component allow for flexible content placement:
    • <slot />: The default slot for the button label or main content.

Rest Props

  • {...rest}: This syntax spreads any additional properties passed to the button component, allowing for further customization and functionality (e.g., event handlers, data attributes).

This structure makes the button component highly customizable and adaptable to different use cases, ensuring it can serve various design needs.

<button
  class:list={[
    "flex",
    "items-center",
    "justify-center",
    "transition-all",
    "duration-300",
    "focus:ring-2",
    "focus:outline-none",
    variant === "accent" && accentClass,
    variant === "default" && defaultClass,
    variant === "alternative" && alternativeaccentClass,
    variant === "muted" && mutedClass,
    variant === "info" && infoClass,
    variant === "success" && successClass,
    variant === "warning" && warningClass,
    variant === "danger" && dangerClass,
    variant === "text" && textClass,
    variant === "link" && linkClass,
    size === "xs" && xs,
    size === "sm" && sm,
    size === "base" && base,
    size === "md" && md,
    size === "lg" && lg,
    size === "xl" && xl,
    gap === "xs" && gapXS,
    gap === "sm" && gapSM,
    gap === "base" && gapBase,
    gap === "md" && gapMD,
    gap === "lg" && gapLG,
    ...additionalClasses,
  ]}
  {...rest}>
  <slot />

</button>

The whole button component

---
const {
  variant,
  size,
  onlyIconSize,
  gap,
  class: className,
  ...rest
} = Astro.props;
// Default button
const defaultClass = [
  "text-white",
  "bg-neutral-900",
  "hover:bg-neutral-800",
  "focus:ring-neutral-500/50",
];
// More variants go here
// Size
const xs = ["h-8", "px-4", "py-2", "text-xs", "font-medium", "rounded-md"];
// More sizes go here
// Gap
const gapXS = ["gap-2"];
// More gaps go here
const additionalClasses = className ? className.split(" ") : [];
---

<button
  class:list={[
    "flex",
    "items-center",
    "justify-center",
    "transition-all",
    "duration-300",
    "focus:ring-2",
    "focus:outline-none",
    variant === "accent" && accentClass,
    variant === "default" && defaultClass,
    variant === "alternative" && alternativeaccentClass,
    variant === "muted" && mutedClass,
    variant === "info" && infoClass,
    variant === "success" && successClass,
    variant === "warning" && warningClass,
    variant === "danger" && dangerClass,
    variant === "text" && textClass,
    variant === "link" && linkClass,
    size === "xs" && xs,
    size === "sm" && sm,
    size === "base" && base,
    size === "md" && md,
    size === "lg" && lg,
    size === "xl" && xl,
    gap === "xs" && gapXS,
    gap === "sm" && gapSM,
    gap === "base" && gapBase,
    gap === "md" && gapMD,
    gap === "lg" && gapLG,
    ...additionalClasses,
  ]}
  {...rest}>
  <slot />

</button>

Using the button

This indicates that the code is using a reusable button component defined in your Astro project. This component accepts several props to control its behavior and appearance.

<Button
  variant="default"
  size="xs">
  Button Default
</Button>

Props:

The props are used to customize the button’s appearance and behavior. Here are some examples that we are using in the component:

  • variant="default": This prop specifies the button’s style variant. In this case, “default” refers to a standard or primary button style with a predefined set of classes that determine its appearance (e.g., background color, text color, border style).
  • size="xs": This prop indicates the button’s size. “xs” stands for “extra small,” which probably applies specific classes to reduce padding, height, and possibly font size compared to larger button sizes.

Adding icons

Now that we have defined our button component, used it on our site, let’s add icons to it. We ar estsrting by creating a new file under the /components/fundations/icson.astro folder in this case and we are going to call it PlaceHolderIcon, so the file name will be PlaceHolderIcon.astro

Ass you can see the the icon also has some props, like we did on our button, and here we adding a variants and sizes. You can also rehuse this props by just swapping the path with the one you want.

---
const { variant, size, class: className, ...rest } = Astro.props;
const xs = ["size-3"];
const sm = ["size-4"];
const base = ["size-5"];
const md = ["size-6"];
const lg = ["size-7"];
const xl = ["size-8"];
---

<svg
  xmlns="http://www.w3.org/2000/svg"
  viewBox="0 0 24 24"
  fill="none"
  stroke="currentColor"
  stroke-width="2"
  stroke-linecap="round"
  stroke-linejoin="round"
  class:list={[
    "icon",
    "icon-tabler",
    "icons-tabler-outline",
    "icon-tabler-layout-grid",
    size === "xs" && xs,
    size === "sm" && sm,
    size === "base" && base,
    size === "md" && md,
    size === "lg" && lg,
    size === "xl" && xl,
    className,
  ]}
  {...rest}>
  <path
    stroke="none"
    d="M0 0h24v24H0z"
    fill="none"
  ></path>
  <path
    d="M4 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"
  ></path>
  <path
    d="M14 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"
  ></path>
  <path
    d="M4 14m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"
  ></path>
  <path
    d="M14 14m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"
  ></path>
</svg>

Now we have our icon but we need to tell the button that has to accpet icons also, so we are going to add a slot to the button component, and we are going to call it left-icon and right-icon for example.

  • <slot name="left-icon" />: A designated area for an icon to the left of the button text.
  • <slot name="right-icon" />: A designated area for an icon to the right of the button text.
<slot name="left-icon" />
<slot />
<slot name="right-icon" />

And this is how we are going to use it on our button component.

<Button variant="default" gap="xs" size="xl">
   <PlaceHolderIcon size="lg" slot="left-icon" />
   Button right icon
</Button>
<Button variant="default" gap="xs" size="xl">
  Button right icon
   <PlaceHolderIcon size="lg" slot="right-icon" />
</Button>

Conclusion

This is it, this is a simple button component that demonstrates how to use Tailwind CSS to create a button with a predefined set of styles, variants, sizes, and icons.

Hope you enjoyed this tutorial and have a great day!

/Michael Andreuzza

Did you like this tutorial? Please share it with your friends!

Get lifetime access to every theme available today for $199 and own them forever. Plus, new themes, lifetime updates, use on unlimited projects and enjoy lifetime support.

— No subscription required!