Usage Examples
Common patterns and real-world examples using styled-cva.
Buttons
Basic Button with Variants
A standard button component with intent and size variants.
import tw from "@styled-cva/react";
const Button = tw.button.cva(
"inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
{
variants: {
$variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "underline-offset-4 hover:underline text-primary",
},
$size: {
default: "h-10 py-2 px-4",
sm: "h-9 px-3 rounded-md",
lg: "h-11 px-8 rounded-md",
icon: "h-10 w-10",
},
},
defaultVariants: {
$variant: "default",
$size: "default",
},
},
);
export default function ButtonExample() {
return (
<div className="flex flex-wrap gap-4 p-4">
<Button>Default</Button>
<Button $variant="secondary">Secondary</Button>
<Button $variant="destructive">Destructive</Button>
<Button $variant="outline">Outline</Button>
<Button $variant="ghost">Ghost</Button>
<Button $variant="link">Link</Button>
</div>
);
}Form Elements
Input with Validation States
An input component that handles error, success, and disabled states.
import tw from "@styled-cva/react";
const Input = tw.input.cva(
"flex h-10 w-full rounded-md border bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
{
variants: {
$state: {
default: "border-input focus-visible:ring-ring",
error:
"border-red-500 focus-visible:ring-red-500 text-red-900 placeholder:text-red-300",
success: "border-green-500 focus-visible:ring-green-500 text-green-900",
},
},
defaultVariants: {
$state: "default",
},
},
);
export default function InputExample() {
return (
<div className="grid w-full max-w-sm items-center gap-4">
<Input placeholder="Default input" />
<Input
$state="error"
placeholder="Error state"
defaultValue="Invalid input"
/>
<Input
$state="success"
placeholder="Success state"
defaultValue="Valid input"
/>
<Input disabled placeholder="Disabled input" />
</div>
);
}Interactive Components
Toggle Switch
A switch component using aria-pressed or a custom prop for state.
import { useState } from "react";
import tw from "@styled-cva/react";
const SwitchRoot = tw.button.cva(
"peer inline-flex h-[24px] w-[44px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50",
{
variants: {
$checked: {
true: "bg-primary",
false: "bg-input",
},
},
},
);
const SwitchThumb = tw.span.cva(
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform",
{
variants: {
$checked: {
true: "translate-x-5",
false: "translate-x-0",
},
},
},
);
export default function SwitchExample() {
const [checked, setChecked] = useState(false);
return (
<SwitchRoot
$checked={checked}
onClick={() => setChecked(!checked)}
role="switch"
aria-checked={checked}
>
<SwitchThumb $checked={checked} />
</SwitchRoot>
);
}Layouts
Card
A composed card component.
import tw from "@styled-cva/react";
const Card = tw.div`rounded-lg border bg-card text-card-foreground shadow-sm`;
const CardHeader = tw.div`flex flex-col space-y-1.5 p-6`;
const CardTitle = tw.h3`text-2xl font-semibold leading-none tracking-tight`;
const CardDescription = tw.p`text-sm text-muted-foreground`;
const CardContent = tw.div`p-6 pt-0`;
const CardFooter = tw.div`flex items-center p-6 pt-0`;
export default function CardExample() {
return (
<Card className="w-[350px]">
<CardHeader>
<CardTitle>Create project</CardTitle>
<CardDescription>Deploy your new project in one-click.</CardDescription>
</CardHeader>
<CardContent>
<p>Project details form goes here...</p>
</CardContent>
<CardFooter className="flex justify-between">
<button className="text-sm">Cancel</button>
<button className="bg-primary rounded px-4 py-2 text-white">
Deploy
</button>
</CardFooter>
</Card>
);
}Polymorphic Components
Using the $as prop to render a button as a link.
import Link from "next/link";
import tw from "@styled-cva/react";
const Button = tw.button.cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2",
);
export default function PolymorphicExample() {
return (
<div className="flex gap-4">
{/* Renders as <button> */}
<Button onClick={() => alert("Clicked!")}>Button</Button>
{/* Renders as <a> */}
<Button
$as="a"
href="https://github.com/alanrsoares/styled-cva"
target="_blank"
rel="noreferrer"
>
GitHub Link
</Button>
{/* Renders as Next.js Link */}
<Button $as={Link} href="/getting-started">
Get Started
</Button>
</div>
);
}Advanced Composition
Extending Components
Styling a custom component or extending an existing one.
import tw from "@styled-cva/react";
// Base component
const BaseBox = tw.div`p-4 border rounded`;
// Extended component overrides styles
const WarningBox = tw(BaseBox)`border-yellow-500 bg-yellow-50 text-yellow-900`;
// Custom React Component
const UserProfile = ({
className,
name,
}: {
className?: string;
name: string;
}) => (
<div className={className}>
<span className="font-bold">{name}</span>
</div>
);
// Styled Custom Component
const StyledProfile = tw(
UserProfile,
)`flex items-center gap-2 p-2 bg-gray-100 rounded-full`;
export default function AdvancedCompositionExample() {
return (
<div className="flex flex-col gap-4">
<BaseBox>Base Box</BaseBox>
<WarningBox>Warning Box</WarningBox>
<StyledProfile name="John Doe" />
</div>
);
}Default Props
Pre-configuring Components
Using .withProps() to set default attributes and variant values.
import tw from "@styled-cva/react";
const IconButton = tw.button
.cva(
"inline-flex items-center justify-center rounded-full p-2 transition-colors",
{
variants: {
$variant: {
primary: "bg-blue-600 text-white hover:bg-blue-700",
ghost: "hover:bg-gray-100 text-gray-600",
},
},
},
)
.withProps({
type: "button",
$variant: "primary",
"aria-label": "Icon button",
});
export default function DefaultPropsExample() {
return (
<div className="flex gap-4">
{/* Uses all defaults */}
<IconButton>
<span className="size-4">š</span>
</IconButton>
{/* Overrides default variant */}
<IconButton $variant="ghost">
<span className="size-4">āļø</span>
</IconButton>
{/* Overrides default aria-label */}
<IconButton aria-label="Settings">
<span className="size-4">š ļø</span>
</IconButton>
</div>
);
}Last updated on