基础组件
Card 卡片
通用内容容器,包含 Header / Title / Description / Content / Footer 子组件。
安装
bash
npx polyui add card登录
tsx
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@polyui/react/card"
import { Button } from "@polyui/react/button"
import { Input } from "@polyui/react/input"
import { Label } from "@polyui/react/label"
export function CardLogin() {
return (
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>Login to your account</CardTitle>
<CardDescription>Enter your email below to login to your account</CardDescription>
</CardHeader>
<CardContent>
<form>
<div className="flex flex-col gap-4">
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" placeholder="m@example.com" />
</div>
<div className="grid gap-2">
<div className="flex items-center">
<Label htmlFor="password">Password</Label>
<Button variant="link" size="xs" className="ml-auto h-auto p-0 text-sm">
Forgot your password?
</Button>
</div>
<Input id="password" type="password" />
</div>
</div>
</form>
</CardContent>
<CardFooter className="flex-col gap-2">
<Button type="submit" className="w-full">
Login
</Button>
<Button variant="outline" className="w-full">
Continue with Google
</Button>
<p className="mt-2 text-center text-sm text-muted-foreground">
Don't have an account?
<Button variant="link" size="xs" className="h-auto p-0">
Sign up
</Button>
</p>
</CardFooter>
</Card>
)
}会议记录
tsx
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@polyui/react/card"
export function CardMeetingNotes() {
return (
<Card className="max-w-md">
<CardHeader>
<CardTitle>Meeting Notes</CardTitle>
<CardDescription>Transcript from the meeting with the client.</CardDescription>
</CardHeader>
<CardContent className="text-sm">
<p>Client requested dashboard redesign with focus on mobile responsiveness.</p>
<ol className="mt-4 flex list-decimal flex-col gap-2 pl-6">
<li>New analytics widgets for daily/weekly metrics</li>
<li>Simplified navigation menu</li>
<li>Dark mode support</li>
<li>Timeline: 6 weeks</li>
<li>Follow-up meeting scheduled for next Tuesday</li>
</ol>
</CardContent>
<CardFooter>
<div className="flex -space-x-2 hover:space-x-1">
{["OS", "HL", "HR", "JW"].map((initials) => (
<div
key={initials}
className="flex size-8 items-center justify-center rounded-full bg-muted text-xs font-medium ring-2 ring-background transition-all duration-300"
>
{initials}
</div>
))}
</div>
</CardFooter>
</Card>
)
}邀请成员
tsx
import { Card, CardHeader, CardTitle, CardContent } from "@polyui/react/card"
import { CircleFadingPlusIcon } from "lucide-react"
export function CardInviteMembers() {
return (
<Card className="w-full max-w-lg">
<CardHeader>
<CardTitle>Team Members</CardTitle>
</CardHeader>
<CardContent className="grid gap-4 sm:grid-cols-2">
<div className="flex items-center gap-4">
<CircleFadingPlusIcon />
<span className="text-sm font-semibold">Invite Member</span>
</div>
{inviteMembers.map((m) => (
<div key={m.initials} className="flex items-center gap-4">
<div className="flex size-10 items-center justify-center rounded-full bg-muted text-xs font-medium">
{m.initials}
</div>
<div className="flex flex-col">
<span className="text-sm font-semibold">{m.name}</span>
<span className="text-muted-foreground text-sm">{m.role}</span>
</div>
</div>
))}
</CardContent>
</Card>
)
}底部图片
tsx
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@polyui/react/card"
export function CardBottomImage() {
return (
<Card className="max-w-md pb-0">
<CardHeader>
<CardTitle>Fluid Gradient Flow</CardTitle>
<CardDescription>A vibrant and abstract background with smooth gradient curves.</CardDescription>
</CardHeader>
<CardContent className="px-0">
<img
src="https://cdn.shadcnstudio.com/ss-assets/components/card/image-1.png?height=280&format=auto"
alt="Fluid Gradient Flow"
className="aspect-video h-64 w-full rounded-b-xl object-cover"
/>
</CardContent>
</Card>
)
}顶部图片
tsx
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@polyui/react/card"
import { Button } from "@polyui/react/button"
export function CardTopImage() {
return (
<Card className="max-w-md pt-0">
<CardContent className="px-0">
<img
src="https://cdn.shadcnstudio.com/ss-assets/components/card/image-2.png?height=280&format=auto"
alt="Ethereal Swirl Gradient"
className="aspect-video h-64 w-full rounded-t-xl object-cover"
/>
</CardContent>
<CardHeader>
<CardTitle>Ethereal Swirl Gradient</CardTitle>
<CardDescription>Smooth, flowing gradients blending rich reds and blues in an abstract swirl.</CardDescription>
</CardHeader>
<CardFooter className="gap-3 max-sm:flex-col max-sm:items-stretch">
<Button>Explore More</Button>
<Button variant="outline">Download Now</Button>
</CardFooter>
</Card>
)
}水平
tsx
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@polyui/react/card"
import { Button } from "@polyui/react/button"
export function CardHorizontal() {
return (
<Card className="max-w-lg py-0 sm:flex-row sm:gap-0">
<CardContent className="grow px-0">
<img
src="https://cdn.shadcnstudio.com/ss-assets/components/card/image-3.png"
alt="Dreamy Colorwave Gradient"
className="size-full rounded-l-xl object-cover"
/>
</CardContent>
<div className="sm:min-w-52">
<CardHeader className="pt-6">
<CardTitle>Dreamy Colorwave Gradient</CardTitle>
<CardDescription>A smooth blend of vibrant pinks, purples, and blues for a magical touch.</CardDescription>
</CardHeader>
<CardFooter className="gap-3 py-6">
<Button className="bg-gradient-to-br from-purple-500 to-pink-500 text-white">Explore More</Button>
</CardFooter>
</div>
</Card>
)
}遮罩
tsx
import { Card, CardTitle, CardContent } from "@polyui/react/card"
export function CardOverlay() {
return (
<Card className="relative max-w-md py-0 before:absolute before:inset-0 before:rounded-xl before:bg-primary/70">
<CardContent className="px-0">
<img
src="https://cdn.shadcnstudio.com/ss-assets/components/card/image-8.png?width=448&height=280&format=auto"
alt="Creative Catalyst"
className="h-64 w-full rounded-xl object-cover"
/>
</CardContent>
<div className="absolute inset-0 flex flex-col justify-start pt-6 px-6">
<CardTitle className="text-primary-foreground">Creative Catalyst</CardTitle>
<p className="mt-2 text-sm text-primary-foreground/80">
Step into a world where imagination takes the lead and every pixel tells a story.
</p>
</div>
</Card>
)
}柔和描边
tsx
import { Card, CardHeader, CardTitle, CardContent } from "@polyui/react/card"
export function CardSoftOutline() {
return (
<div className="flex flex-wrap gap-4">
<Card className="bg-primary/20 max-w-md gap-0">
<CardHeader>
<CardTitle>Design Throwdown</CardTitle>
</CardHeader>
<CardContent className="text-sm text-muted-foreground">
Where passion, pressure, and pixels collide—push your creativity to the edge and show what you are made of.
</CardContent>
</Card>
<Card className="border-primary max-w-md gap-0 bg-transparent shadow-none">
<CardHeader>
<CardTitle>Creative Clash</CardTitle>
</CardHeader>
<CardContent className="text-sm text-muted-foreground">
Step into a space where design skills are tested, ideas come alive, and only the boldest concepts win.
</CardContent>
</Card>
</div>
)
}带标签页
tsx
import { useState } from "react"
import { Card, CardContent } from "@polyui/react/card"
import { cn } from "@polyui/core/lib/utils"
export function CardWithTabs() {
const [activeTab, setActiveTab] = useState("home")
return (
<Card className="w-fit">
<CardContent>
<div className="w-full max-w-sm">
<div className="flex border-b">
{tabItems.map((tab) => (
<button
key={tab.value}
onClick={() => setActiveTab(tab.value)}
className={cn(
"h-10 px-4 text-sm transition-colors",
activeTab === tab.value
? "border-b-2 border-primary font-medium text-foreground"
: "text-muted-foreground hover:text-foreground"
)}
>
{tab.name}
</button>
))}
</div>
<p className="p-4 text-sm text-muted-foreground">{tabItems.find((t) => t.value === activeTab)?.content}</p>
</div>
</CardContent>
</Card>
)
}推文卡片
tsx
import { useState } from "react"
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, CardAction } from "@polyui/react/card"
import { Button } from "@polyui/react/button"
import { HeartIcon, MessageCircleIcon, RepeatIcon, SendIcon, UserPlusIcon, EllipsisIcon, BadgeCheckIcon } from "lucide-react"
import { cn } from "@polyui/core/lib/utils"
export function CardTweet() {
const [liked, setLiked] = useState(true)
return (
<Card className="max-w-md">
<CardHeader className="flex items-center justify-between gap-3">
<div className="flex items-center gap-3">
<div className="flex size-10 items-center justify-center rounded-full bg-muted text-xs font-medium ring-2 ring-ring">
PG
</div>
<div className="flex flex-col gap-0.5">
<CardTitle className="flex items-center gap-1 text-sm">
Philip George <BadgeCheckIcon className="size-4 fill-sky-600 stroke-white dark:fill-sky-400" />
</CardTitle>
<CardDescription>@philip20</CardDescription>
</div>
</div>
<CardAction>
<div className="flex items-center gap-2">
<Button variant="outline" size="sm">
<UserPlusIcon />
Follow
</Button>
<Button variant="ghost" size="icon-sm" aria-label="Toggle menu">
<EllipsisIcon />
</Button>
</div>
</CardAction>
</CardHeader>
<CardContent className="space-y-4 text-sm">
<img
src="https://cdn.shadcnstudio.com/ss-assets/components/card/image-6.png?width=350&format=auto"
alt="Post image"
className="aspect-video w-full rounded-md object-cover"
/>
<p>Lost in the colors of the night 🌌✨ — every gradient tells a story worth exploring.</p>
</CardContent>
<CardFooter className="flex items-center gap-1">
<Button variant="ghost" size="sm" onClick={() => setLiked(!liked)}>
<HeartIcon className={cn(liked && "fill-destructive stroke-destructive")} />
2.1K
</Button>
<Button variant="ghost" size="sm">
<MessageCircleIcon />
1.4K
</Button>
<Button variant="ghost" size="sm">
<RepeatIcon />
669
</Button>
<Button variant="ghost" size="sm">
<SendIcon />
1.1K
</Button>
</CardFooter>
</Card>
)
}产品卡片

tsx
import { useState } from "react"
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@polyui/react/card"
import { Button } from "@polyui/react/button"
import { Badge } from "@polyui/react/badge"
import { HeartIcon } from "lucide-react"
import { cn } from "@polyui/core/lib/utils"
export function CardProduct() {
const [liked, setLiked] = useState(false)
return (
<div className="relative max-w-md overflow-hidden rounded-xl bg-gradient-to-r from-neutral-600 to-violet-300 shadow-lg">
<div className="flex h-56 items-center justify-center">
<img
src="https://cdn.shadcnstudio.com/ss-assets/components/card/image-11.png?width=300&format=auto"
alt="Nike Jordan Air Rev"
className="w-72"
/>
</div>
<Button
size="icon-sm"
onClick={() => setLiked(!liked)}
className="absolute top-4 right-4 rounded-full bg-black/20 hover:bg-black/30"
aria-label="Like"
>
<HeartIcon className={cn(liked ? "fill-destructive stroke-destructive" : "stroke-white")} />
</Button>
<Card className="border-none">
<CardHeader>
<CardTitle>Nike Jordan Air Rev</CardTitle>
<CardDescription className="flex items-center gap-2">
<Badge variant="outline" className="rounded-sm">
EU38
</Badge>
<Badge variant="outline" className="rounded-sm">
Black and White
</Badge>
</CardDescription>
</CardHeader>
<CardContent className="text-sm text-muted-foreground">
Crossing hardwood comfort with off-court flair. '80s-inspired construction with bold details.
</CardContent>
<CardFooter className="justify-between gap-3">
<div className="flex flex-col">
<span className="text-xs font-medium uppercase text-muted-foreground">Price</span>
<span className="text-xl font-semibold">$69.99</span>
</div>
<Button size="lg">Add to cart</Button>
</CardFooter>
</Card>
</div>
)
}用户评价
tsx
import { Card, CardContent, CardFooter } from "@polyui/react/card"
import { StarIcon } from "lucide-react"
export function CardTestimonial() {
return (
<Card className="max-w-md border-none">
<CardContent className="pt-4 text-sm">
<p>
Incredible time-saver! The pre-built components are not only{" "}
<span className="bg-primary/10 px-0.5">visually appealing but also highly customizable</span>, fitting
seamlessly into my projects.
</p>
</CardContent>
<CardFooter className="justify-between gap-3">
<div className="flex items-center gap-3">
<div className="flex size-9 items-center justify-center rounded-full bg-muted text-xs font-medium ring-2 ring-ring">
SG
</div>
<div className="flex flex-col gap-0.5">
<span className="text-sm font-medium">Sam Green</span>
<span className="text-xs text-muted-foreground">@SamG11</span>
</div>
</div>
<div className="flex items-center gap-0.5">
{[1, 2, 3, 4].map((i) => (
<StarIcon
key={i}
className="size-4 fill-amber-500 stroke-amber-500 dark:fill-amber-400 dark:stroke-amber-400"
/>
))}
<StarIcon className="size-4 stroke-amber-500 dark:stroke-amber-400" />
</div>
</CardFooter>
</Card>
)
}可关闭操作
tsx
import { useState } from "react"
import { Card, CardHeader, CardTitle, CardContent, CardAction } from "@polyui/react/card"
import { Button } from "@polyui/react/button"
import { XIcon } from "lucide-react"
export function CardDismissibleAction() {
const [visible, setVisible] = useState(true)
if (!visible) {
return (
<Button variant="outline" size="sm" onClick={() => setVisible(true)}>
Show card
</Button>
)
}
return (
<Card className="relative max-w-lg shadow-none">
<CardHeader>
<CardTitle className="text-center">Have a project in mind?</CardTitle>
<CardAction>
<Button
variant="ghost"
size="icon-sm"
onClick={() => setVisible(false)}
className="rounded-full"
aria-label="Close"
>
<XIcon />
</Button>
</CardAction>
</CardHeader>
<CardContent className="flex flex-col gap-4 text-center text-sm text-muted-foreground">
<p>Let's discuss! Our team is excited to hear about your projects, ideas and questions.</p>
<Button>Contact Our Team</Button>
</CardContent>
</Card>
)
}分组
tsx
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@polyui/react/card"
import { Button } from "@polyui/react/button"
export function CardGroup() {
return (
<div className="flex *:rounded-none *:shadow-none max-xl:flex-col max-xl:*:[&:not(:last-child)]:border-b-0 max-xl:*:first:rounded-t-xl max-xl:*:last:rounded-b-xl xl:*:[&:not(:last-child)]:border-r-0 xl:*:first:rounded-l-xl xl:*:last:rounded-r-xl">
{groupCards.map((card) => (
<Card key={card.title} className="overflow-hidden pt-0">
<CardContent className="px-0">
<img src={card.img} alt={card.title} className="aspect-video w-full object-cover" />
</CardContent>
<CardHeader>
<CardTitle>{card.title}</CardTitle>
<CardDescription>{card.desc}</CardDescription>
</CardHeader>
<CardFooter className="gap-3 max-sm:flex-col max-sm:items-stretch">
<Button>Explore More</Button>
<Button variant="outline">Download Now</Button>
</CardFooter>
</Card>
))}
</div>
)
}聚光灯
tsx
import { useEffect, useRef } from "react"
import { Card, CardHeader, CardTitle, CardContent } from "@polyui/react/card"
export function CardSpotlight() {
const blobRef = useRef<HTMLDivElement>(null)
const fakeBlobRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const handleMouseMove = (ev: MouseEvent) => {
const blob = blobRef.current
const fblob = fakeBlobRef.current
if (!blob || !fblob) return
const rec = fblob.getBoundingClientRect()
blob.style.opacity = "1"
blob.animate(
[
{
transform: `translate(${ev.clientX - rec.left - rec.width / 2}px, ${ev.clientY - rec.top - rec.height / 2}px)`,
},
],
{ duration: 300, fill: "forwards" }
)
}
window.addEventListener("mousemove", handleMouseMove)
return () => window.removeEventListener("mousemove", handleMouseMove)
}, [])
return (
<div className="group bg-border relative overflow-hidden rounded-xl p-px transition-all duration-300">
<Card className="group-hover:bg-card/90 max-w-80 border-none transition-all duration-300 group-hover:backdrop-blur-[20px]">
<CardHeader>
<CardTitle>Hover for the Glow-Up</CardTitle>
</CardHeader>
<CardContent className="text-sm text-muted-foreground">
Glide your cursor here and watch magic unfold — an experience designed just for you.
</CardContent>
</Card>
<div
ref={blobRef}
className="absolute top-0 left-0 size-20 rounded-full bg-sky-600/60 opacity-0 blur-2xl transition-all duration-300 dark:bg-sky-400/60"
/>
<div ref={fakeBlobRef} className="absolute top-0 left-0 size-20 rounded-full" />
</div>
)
}3D 悬停
vue
import { Card, CardHeader, CardTitle, CardContent } from "@polyui/vue/card"
export function Card3DHover() {
return (
<Card class="max-w-md">
<CardHeader>
<CardTitle>Dynamic 3D Hover Card</CardTitle>
</CardHeader>
<CardContent class="space-y-4 text-sm text-muted-foreground">
<img
src="https://cdn.shadcnstudio.com/ss-assets/components/card/image-10.png?width=350&format=auto"
alt="3D hover card"
class="aspect-video w-full rounded-md object-cover"
/>
<p>
Experience interactive depth and motion with this sleek 3D hover effect. Move your cursor to see it come
alive!
</p>
</CardContent>
</Card>
)
}属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
Card · className | string | — | 应用于卡片根元素的额外类名。 |
CardHeader · className | string | — | 应用于卡片头部区域的额外类名,默认含 padding。 |
CardTitle · as | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "h3" | 标题渲染的 HTML 标签,默认为 h3。 |
CardDescription · className | string | — | 应用于卡片描述文字的额外类名,默认为 muted 颜色。 |
CardContent · className | string | — | 应用于卡片内容区域的额外类名,默认含水平 padding。 |
CardFooter · className | string | — | 应用于卡片底部区域的额外类名,默认为 flex 布局。 |








