Epicenter
Local-first, open source apps
Enter your email below to login to your account
<script lang="ts">
import * as Card from "$lib/components/ui/card/index.js";
import { Button } from "$lib/components/ui/button/index.js";
import { Input } from "$lib/components/ui/input/index.js";
import { Label } from "$lib/components/ui/label/index.js";
</script>
<Card.Root class="w-full max-w-sm">
<Card.Header>
<Card.Title>Login to your account</Card.Title>
<Card.Description
>Enter your email below to login to your account</Card.Description
>
<Card.Action>
<Button variant="link">Sign Up</Button>
</Card.Action>
</Card.Header>
<Card.Content>
<form>
<div class="flex flex-col gap-6">
<div class="grid gap-2">
<Label for="email">Email</Label>
<Input id="email" type="email" placeholder="m@example.com" required />
</div>
<div class="grid gap-2">
<div class="flex items-center">
<Label for="password">Password</Label>
<a
href="##"
class="ml-auto inline-block text-sm underline-offset-4 hover:underline"
>
Forgot your password?
</a>
</div>
<Input id="password" type="password" required />
</div>
</div>
</form>
</Card.Content>
<Card.Footer class="flex-col gap-2">
<Button type="submit" class="w-full">Login</Button>
<Button variant="outline" class="w-full">Login with Google</Button>
</Card.Footer>
</Card.Root> Installation #
pnpm dlx shadcn-svelte@latest add cardnpx shadcn-svelte@latest add cardnpx shadcn-svelte@latest add cardbun x shadcn-svelte@latest add cardCopy and paste the following code into your project.
<script lang="ts">
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="card-action"
class={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
{...restProps}
>
{@render children?.()}
</div>
Usage #
<script lang="ts">
import * as Card from "$lib/components/ui/card/index.js";
</script> <Card.Root>
<Card.Header>
<Card.Title>Card Title</Card.Title>
<Card.Description>Card Description</Card.Description>
<Card.Action>Card Action</Card.Action>
</Card.Header>
<Card.Content>
<p>Card Content</p>
</Card.Content>
<Card.Footer>
<p>Card Footer</p>
</Card.Footer>
</Card.Root> Size #
Use the size="sm" prop to set the size of the card to small. The small size variant uses smaller spacing.
Weekly snapshots. No more manual exports.
- Choose a schedule (daily, or weekly).
- Send to channels or specific teammates.
- Include charts, tables, and key metrics.
<script lang="ts">
import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
import * as Card from "$lib/components/ui/card/index.js";
import { Button } from "$lib/components/ui/button/index.js";
const featureName = "Scheduled reports";
const steps = [
"Choose a schedule (daily, or weekly).",
"Send to channels or specific teammates.",
"Include charts, tables, and key metrics."
];
</script>
<Card.Root size="sm" class="mx-auto w-full max-w-xs">
<Card.Header>
<Card.Title>{featureName}</Card.Title>
<Card.Description
>Weekly snapshots. No more manual exports.</Card.Description
>
</Card.Header>
<Card.Content>
<ul class="grid gap-2 py-2 text-sm">
{#each steps as step (step)}
<li class="flex gap-2">
<ChevronRightIcon
class="mt-0.5 size-4 shrink-0 text-muted-foreground"
/>
<span>{step}</span>
</li>
{/each}
</ul>
</Card.Content>
<Card.Footer class="flex-col gap-2">
<Button size="sm" class="w-full">Set up scheduled reports</Button>
<Button variant="outline" size="sm" class="w-full">See what's new</Button>
</Card.Footer>
</Card.Root> Spacing #
In addition to the size prop, you can use the --card-spacing CSS variable to control the spacing between sections and the inset of card parts.
Enter your email below to login to your account
<script lang="ts">
import * as Card from "$lib/components/ui/card/index.js";
import * as ToggleGroup from "$lib/components/ui/toggle-group/index.js";
import { Button } from "$lib/components/ui/button/index.js";
import { Input } from "$lib/components/ui/input/index.js";
import { Label } from "$lib/components/ui/label/index.js";
const spacingOptions = [
{
className: "[--card-spacing:--spacing(4)]",
label: "16px",
value: "4"
},
{
className: "[--card-spacing:--spacing(5)]",
label: "20px",
value: "5"
},
{
className: "[--card-spacing:--spacing(6)]",
label: "24px",
value: "6"
},
{
className: "[--card-spacing:--spacing(8)]",
label: "32px",
value: "8"
}
];
let spacing = $state("4");
let selectedSpacing = $derived(
spacingOptions.find((option) => option.value === spacing)
);
</script>
<div class="mx-auto grid w-full max-w-sm gap-4">
<ToggleGroup.Root
type="single"
bind:value={spacing}
variant="outline"
size="sm"
class="justify-center"
>
{#each spacingOptions as option (option.value)}
<ToggleGroup.Item value={option.value}>{option.label}</ToggleGroup.Item>
{/each}
</ToggleGroup.Root>
<Card.Root class={selectedSpacing?.className}>
<Card.Header>
<Card.Title>Login to your account</Card.Title>
<Card.Description
>Enter your email below to login to your account</Card.Description
>
<Card.Action>
<Button variant="link">Sign Up</Button>
</Card.Action>
</Card.Header>
<Card.Content>
<form>
<div class="flex flex-col gap-6">
<div class="grid gap-2">
<Label for="email-spacing">Email</Label>
<Input
id="email-spacing"
type="email"
placeholder="m@example.com"
required
/>
</div>
<div class="grid gap-2">
<div class="flex items-center">
<Label for="password-spacing">Password</Label>
<a
href="##"
class="ml-auto inline-block text-sm underline-offset-4 hover:underline"
>
Forgot your password?
</a>
</div>
<Input id="password-spacing" type="password" required />
</div>
</div>
</form>
</Card.Content>
<Card.Footer class="flex-col gap-2">
<Button type="submit" class="w-full">Login</Button>
<Button variant="outline" class="w-full">Login with Google</Button>
</Card.Footer>
</Card.Root>
</div> Use negative margins with -mx-(--card-spacing) to make content go edge to edge while keeping it aligned with the card inset. When the edge-to-edge content sits above a footer, use -mb-(--card-spacing) on Card.Content to remove the section gap.
Review the terms before accepting the agreement.
These terms govern your use of the workspace, including access to shared documents, project files, and collaboration tools.
You are responsible for the content you upload and for ensuring that your team has the appropriate permissions to view or edit it.
We may update features or limits as the service evolves. When those changes materially affect your workflow, we will notify your workspace administrators.
By continuing, you agree to keep your account credentials secure and to follow your organization's acceptable use policies.
<script lang="ts">
import * as Card from "$lib/components/ui/card/index.js";
import { Button } from "$lib/components/ui/button/index.js";
</script>
<Card.Root class="mx-auto w-full max-w-sm">
<Card.Header>
<Card.Title>Terms of Service</Card.Title>
<Card.Description
>Review the terms before accepting the agreement.</Card.Description
>
</Card.Header>
<Card.Content class="-mb-(--card-spacing)">
<div
class="-mx-(--card-spacing) max-h-48 space-y-4 overflow-y-scroll border-t bg-muted/50 px-(--card-spacing) py-4 text-sm leading-relaxed"
>
<p>
These terms govern your use of the workspace, including access to shared
documents, project files, and collaboration tools.
</p>
<p>
You are responsible for the content you upload and for ensuring that
your team has the appropriate permissions to view or edit it.
</p>
<p>
We may update features or limits as the service evolves. When those
changes materially affect your workflow, we will notify your workspace
administrators.
</p>
<p>
By continuing, you agree to keep your account credentials secure and to
follow your organization's acceptable use policies.
</p>
</div>
</Card.Content>
<Card.Footer class="justify-end gap-2">
<Button variant="outline">Decline</Button>
<Button>Accept</Button>
</Card.Footer>
</Card.Root> Image #
Add an image before the card header to create a card with an image.
A practical talk on component APIs, accessibility, and shipping faster.
<script lang="ts">
import * as Card from "$lib/components/ui/card/index.js";
import { Badge } from "$lib/components/ui/badge/index.js";
import { Button } from "$lib/components/ui/button/index.js";
</script>
<Card.Root class="relative mx-auto w-full max-w-sm pt-0">
<div class="absolute inset-0 z-30 aspect-video bg-black/35"></div>
<img
src="https://avatar.vercel.sh/shadcn1"
alt="Event cover"
class="relative z-20 aspect-video w-full object-cover brightness-60 grayscale dark:brightness-40"
/>
<Card.Header>
<Card.Action>
<Badge variant="secondary">Featured</Badge>
</Card.Action>
<Card.Title>Design systems meetup</Card.Title>
<Card.Description>
A practical talk on component APIs, accessibility, and shipping faster.
</Card.Description>
</Card.Header>
<Card.Footer>
<Button class="w-full">View Event</Button>
</Card.Footer>
</Card.Root>