Components
Card
A grainy looking card... nothing more, nothing less.
Kimi no Koto ga Daidaidaidaidaisuki na 100-nin no Kanojo
Rentarou Aijou has it all: looks, intelligence, athletic skill, and popularity with peers and mentors alike. Unfortunately, none of these qualities help Rentarou with his love life. On the day of his middle school graduation, he is once again turned down by a girl he confesses to, earning his one-hundredth rejection in a row. Down on his luck, he goes to a matchmaking shrine and wishes to finally get a girlfriend in high school.
When the god of the shrine suddenly appears before him, Rentarou is told he will meet an astronomical total of one hundred soulmates in high school. Though Rentarou initially does not take this foretelling seriously, his doubts disappear when, on the first day of school, he meets two of these soulmates—Hakari Hanazono and Karane Inda—who both confess to him. With fated encounters and love confessions galore, Rentarou's life is about to get a lot more exciting.
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from './card';
export default function CardExample() {
return (
<Card className="max-w-sm">
<CardHeader>
<CardTitle>
Kimi no Koto ga Daidaidaidaidaisuki na 100-nin no Kanojo
</CardTitle>
<CardDescription>
Rentarou Aijou has it all: looks, intelligence, athletic skill, and
popularity with peers and mentors alike. Unfortunately, none of these
qualities help Rentarou with his love life. On the day of his middle
school graduation, he is once again turned down by a girl he confesses
to, earning his one-hundredth rejection in a row. Down on his luck, he
goes to a matchmaking shrine and wishes to finally get a girlfriend in
high school.
</CardDescription>
</CardHeader>
<CardContent>
<p>
When the god of the shrine suddenly appears before him, Rentarou is
told he will meet an astronomical total of one hundred soulmates in
high school. Though Rentarou initially does not take this foretelling
seriously, his doubts disappear when, on the first day of school, he
meets two of these soulmates—Hakari Hanazono and Karane Inda—who both
confess to him. With fated encounters and love confessions galore,
Rentarou's life is about to get a lot more exciting.
</p>
</CardContent>
</Card>
);
}Installation
pnpx shadcn@latest add https://ui.marviuz.me/r/card.jsonnpx shadcn@latest add https://ui.marviuz.me/r/card.jsonyarn dlx shadcn@latest add https://ui.marviuz.me/r/card.jsonbunx shadcn@latest add https://ui.marviuz.me/r/card.jsonCopy and paste the code below into your project and update imports as needed.
import { Slot } from '@radix-ui/react-slot';
import { type ComponentProps } from 'react';
import { cn } from '~/lib/utils';
type AsChild = {
asChild?: boolean;
};
type DivProps = ComponentProps<'div'> & AsChild;
export function Card({ asChild, className, ...props }: DivProps) {
const Comp = asChild ? Slot : 'div';
return (
<Comp
className={cn(
'svg-bg-noise-grain bg-card text-card-foreground grid gap-2 rounded-md border py-6 shadow-sm',
className,
)}
data-slot="card"
{...props}
/>
);
}
export function CardContent({ asChild, className, ...props }: DivProps) {
const Comp = asChild ? Slot : 'div';
return (
<Comp
className={cn('px-6', className)}
data-slot="card-content"
{...props}
/>
);
}
export function CardHeader({ asChild, className, ...props }: DivProps) {
const Comp = asChild ? Slot : 'div';
return (
<Comp
className={cn('grid gap-4 px-6', className)}
data-slot="card-header"
{...props}
/>
);
}
export function CardTitle({
asChild,
className,
...props
}: ComponentProps<'h3'> & AsChild) {
const Comp = asChild ? Slot : 'h3';
return (
<Comp
className={cn('text-2xl font-bold', className)}
data-slot="card-title"
{...props}
/>
);
}
export function CardDescription({
asChild,
className,
...props
}: ComponentProps<'p'> & AsChild) {
const Comp = asChild ? Slot : 'h3';
return (
<Comp
className={cn('text-muted-foreground text-sm', className)}
data-slot="card-description"
{...props}
/>
);
}
And then, add the following code your globals.css file:
@theme {
--svg-noise-grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 700 700' width='700' height='700' opacity='.05'%3E%3Cdefs%3E%3Cfilter id='a' x='-20%25' y='-20%25' width='140%25' height='140%25' filterUnits='objectBoundingBox' primitiveUnits='userSpaceOnUse' color-interpolation-filters='linearRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='10.1' numOctaves='10' seed='15' stitchTiles='stitch' x='0%25' y='0%25' width='100%25' height='100%25' result='turbulence'/%3E%3CfeSpecularLighting surfaceScale='10' specularConstant='1.3' specularExponent='8' lighting-color='%23fff' x='0%25' y='0%25' width='100%25' height='100%25' in='turbulence' result='specularLighting'%3E%3CfeDistantLight azimuth='3' elevation='200'/%3E%3C/feSpecularLighting%3E%3C/filter%3E%3C/defs%3E%3Cpath fill='transparent' d='M0 0h700v700H0z'/%3E%3Cpath fill='%23fff' filter='url(%23a)' d='M0 0h700v700H0z'/%3E%3C/svg%3E%0A");
}
@utility svg-bg-* {
background-image: --value(--svg-*);
}Usage
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from './card';<Card>
<CardHeader>
<CardTitle>Title</CardTitle>
<CardDescription>Description</CardDescription>
</CardHeader>
<CardContent>Content</CardContent>
</Card>