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:
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.
Before we start, make sure you have:
Creating a custom button variant in Nuxt UI involves three steps:
app.config.tsLet's dive in!
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%;
}
}
position: relative & overflow: hidden: Creates a containing box for the pseudo-element and clips any overflow::before pseudo-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 variablez-index: -1 on the pseudo-element ensures the animated background stays behind the button text.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",
],
},
},
},
},
},
});
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 animationborder classes: 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)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);
}
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>
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 animationvar(--ui-primary) for consistent themingz-index layering: Keep animations behind content using negative z-index500ms ease-out creates a smooth, premium feeloverflow: 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.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!
What is a JSON Web Token?
JSON Web Token is a self-contained and compact way for transmitting pieces of information between parties securing as a JSON object. Information is digitally signed so it can be verified. For digital signing, a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.
Nuxt Server-Side Proxy for Open Weather Api
A common problem when using open weather api is Cross-Origin Resource Sharing (CORS) issues. This typically happens when you're trying to make an API call from a web application running in a browser, and the browser blocks the request because the API server does not include the necessary CORS headers in its response. The quickest fix is to add a cors header however, as we do not control the api server we can not add a cors header even if we wanted too.