Card
A composable and flexible card layout component with zero styling by default.
Overview
The Card component provides a composable way to create card layouts with semantic HTML structure. It follows a compound component pattern, offering multiple specialized parts that can be composed together to create rich card interfaces.
Each part of the Card is built as a polymorphic component, allowing you to change the underlying HTML element while maintaining the component's functionality.
Key Features
- Composable Architecture - Mix and match card parts to create the exact layout you need
- Polymorphic Components - Each card part can render as any HTML element via the
as
prop - Zero Styling by Default - Complete freedom to apply your own styles
- Semantic HTML - Default elements follow appropriate HTML semantics (article, header, etc.)
- Slot Support - CardFooter and CardAction support the
asChild
pattern for advanced composition
Installation
# Using pnpm (recommended)
pnpm add @zayne-labs/ui-react
# Using npm
npm install @zayne-labs/ui-react
# Using yarn
yarn add @zayne-labs/ui-react
Basic Usage
import { Card } from "@zayne-labs/ui-react/ui/card";
function BasicCard() {
return (
<Card.Root className="rounded-lg border p-6 shadow-sm">
<Card.Header className="space-y-1.5 pb-4">
<Card.Title>Card Title</Card.Title>
<Card.Description>Card description goes here</Card.Description>
</Card.Header>
<Card.Content className="py-4">This is the main content of the card.</Card.Content>
<Card.Footer className="flex justify-end pt-4">
<button className="rounded bg-blue-500 px-4 py-2 text-white">Action</button>
</Card.Footer>
</Card.Root>
);
}
Advanced Usage
Polymorphic Rendering
Change the underlying HTML element of any card part using the as
prop:
function PolymorphicCard() {
return (
<Card.Root as="div" className="rounded-lg border p-6">
<Card.Header as="section">
<Card.Title as="h2">Custom Elements</Card.Title>
<Card.Description as="div">Using different HTML elements</Card.Description>
</Card.Header>
<Card.Content as="section">Content in a section instead of a div.</Card.Content>
<Card.Footer as="div">Custom footer</Card.Footer>
</Card.Root>
);
}
Using asChild with Footer
The asChild
prop allows you to use your own component as the root element:
function CardWithAsChild() {
return (
<Card.Root className="rounded-lg border p-6">
{/* Other card parts */}
<Card.Footer asChild>
<div className="flex items-center justify-between pt-4">
<button>Cancel</button>
<button>Submit</button>
</div>
</Card.Footer>
</Card.Root>
);
}
Card with Action Button
function CardWithAction() {
return (
<Card.Root className="rounded-lg border p-6">
<Card.Header>
<Card.Title>Card with Action</Card.Title>
<Card.Description>This card has an action button in the header</Card.Description>
<Card.Action className="text-blue-500 hover:text-blue-700">Edit</Card.Action>
</Card.Header>
<Card.Content>
Content of the card with an action button positioned in the top-right corner of the header.
</Card.Content>
</Card.Root>
);
}
Nested Cards
function NestedCards() {
return (
<Card.Root className="rounded-lg border p-6">
<Card.Header>
<Card.Title>Parent Card</Card.Title>
</Card.Header>
<Card.Content className="py-4">
<Card.Root className="rounded-lg border bg-gray-50 p-4">
<Card.Header>
<Card.Title>Nested Card</Card.Title>
</Card.Header>
<Card.Content>This is a nested card within the parent card.</Card.Content>
</Card.Root>
</Card.Content>
</Card.Root>
);
}
API Reference
Card.Root
The main container for the card.
Props:
as?: React.ElementType
- The element to render as (default: 'article')className?: string
- Additional CSS classeschildren: React.ReactNode
- Card content...props
- All other props are passed to the underlying element
Card.Action
Action button or controls area positioned in the top-right corner of the header.
Props:
as?: React.ElementType
- The element to render as (default: 'div')className?: string
- Additional CSS classeschildren: React.ReactNode
- Action content...props
- All other props are passed to the underlying element
Card.Footer
Container for the card footer content.
Props:
as?: React.ElementType
- The element to render as (default: 'footer')asChild?: boolean
- Use your own component as the root element via the Slot patternclassName?: string
- Additional CSS classeschildren: React.ReactNode
- Card content...props
- All other props are passed to the underlying element
Card.Title
The title component for the card.
Props:
as?: React.ElementType
- The element to render as (default: 'h3')className?: string
- Additional CSS classeschildren: React.ReactNode
- Title content...props
- All other props are passed to the underlying element
Default Styling:
font-semibold
- Applied by default
Card.Description
The description component for the card.
Props:
as?: React.ElementType
- The element to render as (default: 'p')className?: string
- Additional CSS classeschildren: React.ReactNode
- Description content...props
- All other props are passed to the underlying element
Default Styling:
text-zu-muted-foreground text-sm
- Applied by default
Card.Content
Container for the main content of the card.
Props:
as?: React.ElementType
- The element to render as (default: 'div')className?: string
- Additional CSS classeschildren: React.ReactNode
- Card content...props
- All other props are passed to the underlying element
Card.Footer
Container for the card footer content.
Props:
as?: React.ElementType
- The element to render as (default: 'footer')asChild?: boolean
- Whether to merge props onto the immediate child (default: false)className?: string
- Additional CSS classeschildren: React.ReactNode
- Footer content...props
- All other props are passed to the underlying element
Styling
Approach
The Card component follows a zero-styling philosophy, with minimal default styles:
Card.Title
has afont-semibold leading-none
styleCard.Description
hastext-zu-muted-foreground text-sm
styles
All other styling is entirely up to you, giving complete freedom to match your design system.
Styling Best Practices
Add border and padding to Card.Root:
tsx<Card.Root className="rounded-lg border p-6" />
Use spacing between card sections:
tsx<Card.Header className="pb-4" /> <Card.Content className="py-4" /> <Card.Footer className="pt-4" />
Consider using a consistent approach to shadows:
tsx<Card.Root className="shadow-sm transition-shadow duration-200 hover:shadow-md" />
For clickable cards, remember accessibility:
tsx<Card.Root as="button" className="block w-full cursor-pointer text-left hover:bg-gray-50" onClick={() => console.log("Card clicked")} />
Accessibility
When using Card components, consider these accessibility best practices:
- Use appropriate heading levels for
Card.Title
based on the document structure - Ensure sufficient color contrast between text and background
- If creating clickable cards, ensure they have appropriate keyboard focus styles and ARIA attributes
Example: Complete Card with Custom Styling
import { Card } from "@zayne-labs/ui-react/ui";
function ProductCard({ product }) {
return (
<Card.Root className="overflow-hidden rounded-lg border shadow-sm transition-shadow duration-200 hover:shadow-md">
<img src={product.image} alt={product.name} className="h-48 w-full object-cover" />
<Card.Header className="p-4">
<Card.Title className="text-xl font-bold">{product.name}</Card.Title>
<Card.Description className="text-gray-600">{product.category}</Card.Description>
</Card.Header>
<Card.Content className="px-4 pb-2">
<p>{product.description}</p>
<p className="mt-2 text-lg font-bold">${product.price.toFixed(2)}</p>
</Card.Content>
<Card.Footer className="flex items-center justify-between bg-gray-50 p-4">
<button className="text-blue-500 hover:underline">Details</button>
<button className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
Add to Cart
</button>
</Card.Footer>
</Card.Root>
);
}