Random Access Components

Skew Button

Button with skew effect, accessible and customizable

TailwindCSS iconTailwind CSS
React IconReact
Next.js IconNext.js

Demo

import SkewButton from "@/components/ui/skew-button";
import { SearchIcon } from "lucide-react";

function SkewButtonDemo() {
    return (
        <div className="flex items-center justify-center gap-4">
            <SkewButton>Skew button</SkewButton>
            <SkewButton as="link" href="#" direction="right">
                I&apos;m a link
            </SkewButton>
            <SkewButton variant="icon" direction="center">
                <SearchIcon className="size-5" />
            </SkewButton>
        </div>
    )
}

export { SkewButtonDemo };

Installation

npm install class-variance-authority
pnpm add class-variance-authority
yarn add class-variance-authority
bun add class-variance-authority

Copy and paste the following code into your project.

skew-button.tsx
import type { ComponentProps } from "react";
import Link from "next/link";
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils";

const skewButtonVariants = cva("flex [corner-shape:squircle] whitespace-nowrap rounded-2xl border border-primary text-xs font-medium transition-all md:text-base hover:-translate-y-1 focus:-translate-y-1 active:translate-y-0 bg-background text-primary disabled:translate-y-0 disabled:rotate-0 disabled:text-secondary disabled:border-secondary disabled:cursor-not-allowed disabled:pointer-events-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:ring-primary outline-none", {
    variants: {
        direction: {
            left: "origin-top-left -rotate-2 hover:-rotate-3 active:-rotate-1 hover:skew-y-2 active:skew-y-0",
            right: "origin-top-right rotate-2 hover:rotate-3 active:rotate-1 hover:-skew-y-2 active:skew-y-0",
            center: "origin-center -translate-y-1",
        },
        variant: {
            default: "px-12 py-3",
            icon: "p-3 w-fit",
        }
    },
    defaultVariants: {
        direction: "left",
        variant: "default",
    }
})

type SkewButtonGenericProps = {
    shadowClassName?: string;
}

type SkewButtonAsButton = { as?: "button" } & ComponentProps<"button">
type SkewButtonAsLink = { as: "link" } & ComponentProps<typeof Link>;

type SkewButtonProps = (SkewButtonAsButton | SkewButtonAsLink) & SkewButtonGenericProps & VariantProps<typeof skewButtonVariants>;

function SkewButton(props: SkewButtonProps) {
    const { as = "button", className, children, direction = "left", variant = "default", shadowClassName, ...rest } = props;

    return (
        <div className={cn("rounded-2xl transition-colors bg-primary w-auto [corner-shape:squircle]", shadowClassName)}>
            {as === "link" ? (
                <Link className={cn(skewButtonVariants({ direction, variant }), className)} {...(rest as ComponentProps<typeof Link>)}>
                    {children}
                </Link>
            ) : (
                <button type="button" className={cn(skewButtonVariants({ direction, variant }), className)} {...(rest as ComponentProps<"button">)}>
                    {children}
                </button>
            )}
        </div>
    );
}

export default SkewButton;

Usage

import SkewButton from "@/components/ui/skew-button";

Basic

<SkewButton>Skew button</SkewButton>
<SkewButton as="link" href="/some-page">
    I'm a link
</SkewButton>

Icon

<SkewButton variant="icon">
    <SearchIcon className="size-5" />
</SkewButton>

Props

It accepts all the props of a native button element. When as="link", it accepts all Next.js Link props instead. Additionally:

PropTypeDefaultDescription
as"button" | "link""button"Renders as a button or a Next.js Link.
direction"left" | "right" | "center""left"The skew direction of the button.
variant"default" | "icon""default"The variant of the button.
shadowClassNamestringClass name for the shadow wrapper element.

On this page