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 Variants
Circle Variants
A showcase of custom button animations built with Nuxt UI and CSS pseudo-elements.
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:
- Define the CSS animation in your global stylesheet
- Register the variant in
app.config.ts - 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:
@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
position: relative&overflow: hidden: Creates a containing box for the pseudo-element and clips any overflow::beforepseudo-element: Creates an invisible layer behind the buttontransform: skewX(35deg): Gives the sliding background its angled edgewidth: 0%→width: 150%: Animates from invisible to fully covering on hovervar(--ui-primary): Uses Nuxt UI's theme color variable
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:
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 defaultbtn-skew: Our custom CSS class with the animationborderclasses: Creates a visible borderbg-default: Uses the default background colorhover:border-primary: Border turns primary color on hoverhover: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:
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:
/* 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:
<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:
/* 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:
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:
<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 filltransform: translate(-50%, -50%)to center the animation- Both width and height animate together for uniform expansion
Key Takeaways
- CSS Variables: Use Nuxt UI's CSS variables like
var(--ui-primary)for consistent theming - Compound Variants: Allow you to combine variant + color for specific styling
- Pseudo-elements: Perfect for creating animated backgrounds without extra DOM elements
z-indexlayering: Keep animations behind content using negative z-index- Transition timing: The
500ms ease-outcreates a smooth, premium feel
Common Pitfalls
overflow: hidden on the button to clip the animated pseudo-element.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!