Articles

Adding Custom Button Variants in Nuxt UI

Jan 17 7 min read nuxt
nuxtnuxt-uicsstips&tricks

Learn how to extend Nuxt UI's button component with custom variants like a stunning skew animation effect. We'll explore app.config.ts configuration, CSS pseudo-elements, and compound variants to create unique button styles that match your brand.

Here's what we'll be building - try hovering over the buttons:

Standard / Icons:

Circular / States:

The Challenge

Nuxt UI provides excellent built-in button variants like solid, outline, soft, and ghost. But what if you want something more unique? Something that makes your buttons stand out with eye-catching animations?

In this tutorial, I'll show you how I created a custom skew button variant that features an animated background that sweeps across the button on hover - a signature effect for my portfolio.

Prerequisites

Before we start, make sure you have:

  • Nuxt project with Nuxt UI v4 installed
  • Basic understanding of Tailwind CSS
  • Familiarity with CSS pseudo-elements

The Approach

Creating a custom button variant in Nuxt UI involves three steps:

  1. Define the CSS animation in your global stylesheet
  2. Register the variant in app.config.ts
  3. Add compound variants for different color options

Let's dive in!

Step 1: The CSS Animation

First, we need to create the skew animation using CSS pseudo-elements. Add this to your assets/css/main.css:

main.css
@layer base {
  /* Skew Button Animation */
  .btn-skew {
    position: relative;
    overflow: hidden;
    z-index: 0;
  }

  .btn-skew::before {
    content: "";
    position: absolute;
    top: 0;
    left: -25px;
    height: 100%;
    width: 0%;
    background-color: var(--ui-primary);
    transform: skewX(35deg);
    z-index: -1;
    transition: width 500ms ease-out;
  }

  .btn-skew:hover::before {
    width: 150%;
  }
}

How It Works

  1. position: relative & overflow: hidden: Creates a containing box for the pseudo-element and clips any overflow
  2. ::before pseudo-element: Creates an invisible layer behind the button
  3. transform: skewX(35deg): Gives the sliding background its angled edge
  4. width: 0%width: 150%: Animates from invisible to fully covering on hover
  5. var(--ui-primary): Uses Nuxt UI's theme color variable
The z-index: -1 on the pseudo-element ensures the animated background stays behind the button text.

Step 2: Register the Variant

Now we register our custom variant in app.config.ts. This is where Nuxt UI's extensibility shines:

app.config.ts
export default defineAppConfig({
  ui: {
    colors: {
      primary: "red",
      neutral: "zinc",
    },
    button: {
      variants: {
        variant: {
          skew: {
            base: [
              "btn-skew",
              "border border-neutral-300 dark:border-neutral-700",
              "bg-default",
              "transition-all duration-300",
              "hover:border-primary",
              "hover:text-white dark:hover:text-default",
              "disabled:cursor-not-allowed",
              "disabled:opacity-50",
            ],
          },
        },
      },
    },
  },
});

Breaking Down the Configuration

  • variants.variant.skew: Defines a new variant called "skew"
  • base: An array of Tailwind classes applied by default
  • btn-skew: Our custom CSS class with the animation
  • border classes: Creates a visible border
  • bg-default: Uses the default background color
  • hover:border-primary: Border turns primary color on hover
  • hover:text-white: Text turns white on hover (matching the primary background)

Step 3: Add Compound Variants for Colors

To support different colors with our skew variant, we use compound variants:

app.config.ts
export default defineAppConfig({
  ui: {
    button: {
      // ... variants from above
      compoundVariants: [
        {
          variant: "skew",
          color: "primary",
          class: "btn-skew",
        },
        {
          variant: "skew",
          color: "success",
          class: "btn-skew btn-skew-success hover:border-success",
        },
        {
          variant: "skew",
          color: "info",
          class: "btn-skew btn-skew-info hover:border-info",
        },
        {
          variant: "skew",
          color: "warning",
          class: "btn-skew btn-skew-warning hover:border-warning",
        },
        {
          variant: "skew",
          color: "neutral",
          class: "btn-skew btn-skew-neutral hover:border-neutral",
        },
      ],
    },
  },
});

Now add the corresponding CSS for each color:

main.css
/* Alternative skew variants with different colors */
.btn-skew-success::before {
  background-color: var(--ui-success);
}

.btn-skew-info::before {
  background-color: var(--ui-info);
}

.btn-skew-warning::before {
  background-color: var(--ui-warning);
}

.btn-skew-neutral::before {
  background-color: var(--ui-bg-inverted);
}

Usage

Now you can use your custom variant just like any built-in variant:

example.vue
<template>
  <div class="flex gap-4">
    <!-- Primary skew button -->
    <UButton variant="skew" color="primary">
      Get Started
    </UButton>

    <!-- Success skew button -->
    <UButton variant="skew" color="success">
      Save Changes
    </UButton>

    <!-- With an icon -->
    <UButton variant="skew" color="primary" icon="lucide:download">
      Download
    </UButton>
  </div>
</template>

Bonus: Circular Button Variant

For circular buttons like a scroll-to-top button, the skew effect doesn't work well because of the rounded shape. Here's a variant that expands from the center instead.

First, add the CSS:

main.css
/* Circular Button Animation - for rounded-full buttons */
.btn-skew-circle {
  position: relative;
  overflow: hidden;
  z-index: 0;
}

.btn-skew-circle::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0%;
  height: 0%;
  background-color: var(--ui-primary);
  border-radius: 50%;
  transform: translate(-50%, -50%);
  z-index: -1;
  transition: width 300ms ease-out, height 300ms ease-out;
}

.btn-skew-circle:hover::before {
  width: 150%;
  height: 150%;
}

Then register it as a variant in app.config.ts:

app.config.ts
button: {
  variants: {
    variant: {
      skew: { /* ... */ },
      "skew-circle": {
        base: [
          "btn-skew-circle",
          "rounded-full",
          "border border-neutral-300 dark:border-neutral-700",
          "bg-default",
          "transition-all duration-300",
          "hover:border-primary",
          "hover:text-white dark:hover:text-default",
          "disabled:cursor-not-allowed",
          "disabled:opacity-50",
        ],
      },
    },
  },
  compoundVariants: [
    // ... other variants
    {
      variant: "skew-circle",
      color: "primary",
      class: "btn-skew-circle",
    },
  ],
}

Now you can use it cleanly without adding extra classes:

ScrollToTop.vue
<UButton
  icon="lucide:arrow-up"
  variant="skew-circle"
  color="primary"
  aria-label="Scroll to top"
/>

The key difference is that skew-circle uses:

  • border-radius: 50% on the pseudo-element for a circular fill
  • transform: translate(-50%, -50%) to center the animation
  • Both width and height animate together for uniform expansion

Key Takeaways

  1. CSS Variables: Use Nuxt UI's CSS variables like var(--ui-primary) for consistent theming
  2. Compound Variants: Allow you to combine variant + color for specific styling
  3. Pseudo-elements: Perfect for creating animated backgrounds without extra DOM elements
  4. z-index layering: Keep animations behind content using negative z-index
  5. Transition timing: The 500ms ease-out creates a smooth, premium feel

Common Pitfalls

Overflow Hidden: Always set overflow: hidden on the button to clip the animated pseudo-element.
Z-Index Context: Make sure your pseudo-element has z-index: -1 and the parent has z-index: 0 to create a proper stacking context.

Conclusion

Nuxt UI's configuration system makes it incredibly easy to extend the component library with custom variants. By combining CSS pseudo-elements with the app.config.ts configuration, you can create unique, branded button styles that work seamlessly with Nuxt UI's theming system.

The skew effect adds a touch of premium interactivity to any button, making your UI feel more polished and engaging. Try experimenting with different animations, timing functions, and transform properties to create your own signature effects!

Resources

Thank You

All rights reserved.
Hasib • © 2026