Your page content goes here.
Blocks
Ready-made compositions of useVyre components for whole sections — copy, paste, and adapt. They are a starting point, not fixed components. Your AI agent knows these patterns too: they're part of the AI context, so it composes pages correctly.
Auth card
A centered sign-in card. For a login or sign-in screen.
Sign in
Welcome back. Enter your details.
import {
Card, CardBody, Field, Input, Button, Checkbox, Heading, Text, Stack,
} from "@usevyre/react";
export function AuthCard() {
return (
<Card variant="elevated" style={{ maxWidth: 420, margin: "0 auto" }}>
<CardBody>
<Stack direction="column" gap="md">
<div>
<Heading size="lg">Sign in</Heading>
<Text color="muted">Welcome back. Enter your details.</Text>
</div>
<Field label="Email">
<Input type="email" placeholder="you@example.com" />
</Field>
<Field label="Password">
<Input type="password" placeholder="••••••••" />
</Field>
<label style={{ display: "flex", alignItems: "center", gap: "var(--vyre-spacing-2)" }}>
<Checkbox /> Remember me
</label>
<Button variant="accent" style={{ width: "100%" }}>Sign in</Button>
<Stack direction="row" gap="sm">
<Button variant="secondary" style={{ flex: 1 }}>GitHub</Button>
<Button variant="secondary" style={{ flex: 1 }}>Google</Button>
</Stack>
</Stack>
</CardBody>
</Card>
);
} <script setup lang="ts">
import {
Card, CardBody, Field, Input, Button, Checkbox, Heading, Text, Stack,
} from "@usevyre/vue";
</script>
<template>
<Card variant="elevated" :style="{ maxWidth: '420px', margin: '0 auto' }">
<CardBody>
<Stack direction="column" gap="md">
<div>
<Heading size="lg">Sign in</Heading>
<Text color="muted">Welcome back. Enter your details.</Text>
</div>
<Field label="Email">
<Input type="email" placeholder="you@example.com" />
</Field>
<Field label="Password">
<Input type="password" placeholder="••••••••" />
</Field>
<label :style="{ display: 'flex', alignItems: 'center', gap: 'var(--vyre-spacing-2)' }">
<Checkbox /> Remember me
</label>
<Button variant="accent" :style="{ width: '100%' }">Sign in</Button>
<Stack direction="row" gap="sm">
<Button variant="secondary" :style="{ flex: 1 }">GitHub</Button>
<Button variant="secondary" :style="{ flex: 1 }">Google</Button>
</Stack>
</Stack>
</CardBody>
</Card>
</template> Stats row
Headline KPIs for the top of a dashboard.
import { Card, CardBody, StatGroup, Stat } from "@usevyre/react";
export function StatsRow() {
return (
<Card style={{ width: "100%" }}>
<CardBody>
<StatGroup>
<Stat label="Revenue" value="$48,200" delta={12.5} trend="up" deltaLabel="vs last month" />
<Stat label="Active users" value="2,340" delta={3.1} trend="up" />
<Stat label="Churn" value="1.8%" delta={-0.4} trend="down" deltaLabel="lower is better" />
</StatGroup>
</CardBody>
</Card>
);
} <script setup lang="ts">
import { Card, CardBody, StatGroup, Stat } from "@usevyre/vue";
</script>
<template>
<Card :style="{ width: '100%' }">
<CardBody>
<StatGroup>
<Stat label="Revenue" value="$48,200" :delta="12.5" trend="up" delta-label="vs last month" />
<Stat label="Active users" value="2,340" :delta="3.1" trend="up" />
<Stat label="Churn" value="1.8%" :delta="-0.4" trend="down" delta-label="lower is better" />
</StatGroup>
</CardBody>
</Card>
</template> Empty state
A friendly zero-data screen with a call to action.
import { EmptyState, Button } from "@usevyre/react";
export function EmptyStateBlock() {
return (
<EmptyState
title="No projects yet"
description="Create your first project to get started."
>
<Button variant="accent">New project</Button>
</EmptyState>
);
} <script setup lang="ts">
import { EmptyState, Button } from "@usevyre/vue";
</script>
<template>
<EmptyState
title="No projects yet"
description="Create your first project to get started."
>
<Button variant="accent">New project</Button>
</EmptyState>
</template> Pricing section
A three-tier pricing grid with a highlighted plan.
Starter
$0 /mo
1 project
Community support
Pro
Popular$19 /mo
Unlimited projects
Email support
Analytics
Team
$49 /mo
Everything in Pro
SSO
Audit log
import { Grid, Card, CardBody, Badge, Heading, Text, Button, Stack } from "@usevyre/react";
const plans = [
{ name: "Starter", price: "$0", features: ["1 project", "Community support"], featured: false },
{ name: "Pro", price: "$19", features: ["Unlimited projects", "Email support", "Analytics"], featured: true },
{ name: "Team", price: "$49", features: ["Everything in Pro", "SSO", "Audit log"], featured: false },
];
export function PricingSection() {
return (
<Grid columns={3} gap="lg">
{plans.map((plan) => (
<Card key={plan.name} variant={plan.featured ? "elevated" : "outlined"}>
<CardBody>
<Stack direction="column" gap="md">
<Stack direction="row" gap="sm" align="center">
<Heading size="md">{plan.name}</Heading>
{plan.featured && <Badge variant="success">Popular</Badge>}
</Stack>
<Heading size="xl">{plan.price}<Text as="span" color="muted"> /mo</Text></Heading>
<Stack direction="column" gap="xs">
{plan.features.map((f) => <Text key={f}>{f}</Text>)}
</Stack>
<Button variant={plan.featured ? "accent" : "secondary"} style={{ width: "100%" }}>
Choose {plan.name}
</Button>
</Stack>
</CardBody>
</Card>
))}
</Grid>
);
} <script setup lang="ts">
import { Grid, Card, CardBody, Badge, Heading, Text, Button, Stack } from "@usevyre/vue";
const plans = [
{ name: "Starter", price: "$0", features: ["1 project", "Community support"], featured: false },
{ name: "Pro", price: "$19", features: ["Unlimited projects", "Email support", "Analytics"], featured: true },
{ name: "Team", price: "$49", features: ["Everything in Pro", "SSO", "Audit log"], featured: false },
];
</script>
<template>
<Grid :columns="3" gap="lg">
<Card v-for="plan in plans" :key="plan.name" :variant="plan.featured ? 'elevated' : 'outlined'">
<CardBody>
<Stack direction="column" gap="md">
<Stack direction="row" gap="sm" align="center">
<Heading size="md">{{ plan.name }}</Heading>
<Badge v-if="plan.featured" variant="success">Popular</Badge>
</Stack>
<Heading size="xl">{{ plan.price }}<Text as="span" color="muted"> /mo</Text></Heading>
<Stack direction="column" gap="xs">
<Text v-for="f in plan.features" :key="f">{{ f }}</Text>
</Stack>
<Button :variant="plan.featured ? 'accent' : 'secondary'" :style="{ width: '100%' }">
Choose {{ plan.name }}
</Button>
</Stack>
</CardBody>
</Card>
</Grid>
</template> Settings panel
An account / preferences panel with fields and toggles.
Settings
import { Card, CardBody, Heading, Field, Input, Switch, Button, Stack, Text } from "@usevyre/react";
export function SettingsPanel() {
return (
<Card variant="outlined" style={{ maxWidth: 560 }}>
<CardBody>
<Stack direction="column" gap="lg">
<Heading size="lg">Settings</Heading>
<Field label="Display name">
<Input placeholder="Ada Lovelace" />
</Field>
<Field label="Email">
<Input type="email" placeholder="ada@example.com" />
</Field>
<label style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
<Text>Email notifications</Text>
<Switch />
</label>
<label style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
<Text>Two-factor authentication</Text>
<Switch />
</label>
<Stack direction="row" gap="sm" justify="end">
<Button variant="ghost">Cancel</Button>
<Button variant="accent">Save changes</Button>
</Stack>
</Stack>
</CardBody>
</Card>
);
} <script setup lang="ts">
import { Card, CardBody, Heading, Field, Input, Switch, Button, Stack, Text } from "@usevyre/vue";
</script>
<template>
<Card variant="outlined" :style="{ maxWidth: '560px' }">
<CardBody>
<Stack direction="column" gap="lg">
<Heading size="lg">Settings</Heading>
<Field label="Display name">
<Input placeholder="Ada Lovelace" />
</Field>
<Field label="Email">
<Input type="email" placeholder="ada@example.com" />
</Field>
<label :style="{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }">
<Text>Email notifications</Text>
<Switch />
</label>
<label :style="{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }">
<Text>Two-factor authentication</Text>
<Switch />
</label>
<Stack direction="row" gap="sm" justify="end">
<Button variant="ghost">Cancel</Button>
<Button variant="accent">Save changes</Button>
</Stack>
</Stack>
</CardBody>
</Card>
</template> App shell
The application frame — collapsible sidebar nav + an app bar over the content.
import {
AppLayout, Sidebar, SidebarHeader, SidebarContent, SidebarSection,
SidebarItem, SidebarFooter, AppShell, AppBar, SidebarTrigger, PageContent,
Heading,
} from "@usevyre/react";
export function AppShellLayout() {
return (
<AppLayout>
<Sidebar>
<SidebarHeader title="Acme" />
<SidebarContent>
<SidebarSection>
<SidebarItem href="/" active>Dashboard</SidebarItem>
<SidebarItem href="/customers">Customers</SidebarItem>
<SidebarItem href="/billing" badge={3}>Billing</SidebarItem>
</SidebarSection>
</SidebarContent>
<SidebarFooter>
<SidebarItem href="/settings">Settings</SidebarItem>
</SidebarFooter>
</Sidebar>
<AppShell>
<AppBar>
<SidebarTrigger />
<Heading size="md">Dashboard</Heading>
</AppBar>
<PageContent>
{/* page content goes here */}
</PageContent>
</AppShell>
</AppLayout>
);
} <script setup lang="ts">
import {
AppLayout, Sidebar, SidebarHeader, SidebarContent, SidebarSection,
SidebarItem, SidebarFooter, AppShell, AppBar, SidebarTrigger, PageContent,
Heading,
} from "@usevyre/vue";
</script>
<template>
<AppLayout>
<Sidebar>
<SidebarHeader title="Acme" />
<SidebarContent>
<SidebarSection>
<SidebarItem href="/" active>Dashboard</SidebarItem>
<SidebarItem href="/customers">Customers</SidebarItem>
<SidebarItem href="/billing" :badge="3">Billing</SidebarItem>
</SidebarSection>
</SidebarContent>
<SidebarFooter>
<SidebarItem href="/settings">Settings</SidebarItem>
</SidebarFooter>
</Sidebar>
<AppShell>
<AppBar>
<SidebarTrigger />
<Heading size="md">Dashboard</Heading>
</AppBar>
<PageContent>
<!-- page content goes here -->
</PageContent>
</AppShell>
</AppLayout>
</template> Page header
A title row with breadcrumb and primary actions — the top of most pages.
Projects
import { Breadcrumb, BreadcrumbItem, Heading, Button, Stack } from "@usevyre/react";
export function PageHeader() {
return (
<Stack direction="column" gap="sm">
<Breadcrumb>
<BreadcrumbItem href="/">Home</BreadcrumbItem>
<BreadcrumbItem current>Projects</BreadcrumbItem>
</Breadcrumb>
<Stack direction="row" align="center" justify="between">
<Heading size="xl">Projects</Heading>
<Stack direction="row" gap="sm">
<Button variant="secondary">Export</Button>
<Button variant="accent">New project</Button>
</Stack>
</Stack>
</Stack>
);
} <script setup lang="ts">
import { Breadcrumb, BreadcrumbItem, Heading, Button, Stack } from "@usevyre/vue";
</script>
<template>
<Stack direction="column" gap="sm">
<Breadcrumb>
<BreadcrumbItem href="/">Home</BreadcrumbItem>
<BreadcrumbItem current>Projects</BreadcrumbItem>
</Breadcrumb>
<Stack direction="row" align="center" justify="between">
<Heading size="xl">Projects</Heading>
<Stack direction="row" gap="sm">
<Button variant="secondary">Export</Button>
<Button variant="accent">New project</Button>
</Stack>
</Stack>
</Stack>
</template> Data table page
Heading, search toolbar, table and pagination — the common list/CRUD screen.
Customers
| Name | Plan | Status |
|---|---|---|
| Acme Inc | Pro | Active |
| Globex | Starter | Trial |
| Initech | Business | Active |
import { useState } from "react";
import {
Stack, Heading, Input, Button, Table, TableHead, TableBody, TableRow,
TableHeader, TableCell, Badge, Pagination,
} from "@usevyre/react";
const rows = [
{ name: "Acme Inc", plan: "Pro", status: "Active" },
{ name: "Globex", plan: "Starter", status: "Trial" },
{ name: "Initech", plan: "Business", status: "Active" },
];
export function DataTablePage() {
const [page, setPage] = useState(1);
const [query, setQuery] = useState("");
return (
<Stack direction="column" gap="md">
<Stack direction="row" align="center" justify="between">
<Heading size="lg">Customers</Heading>
<Stack direction="row" gap="sm">
<Input placeholder="Search…" value={query} onChange={(e) => setQuery(e.target.value)} />
<Button variant="accent">Add customer</Button>
</Stack>
</Stack>
<Table>
<TableHead>
<TableRow>
<TableHeader>Name</TableHeader>
<TableHeader>Plan</TableHeader>
<TableHeader>Status</TableHeader>
</TableRow>
</TableHead>
<TableBody>
{rows.map((r) => (
<TableRow key={r.name}>
<TableCell>{r.name}</TableCell>
<TableCell>{r.plan}</TableCell>
<TableCell>
<Badge variant={r.status === "Active" ? "success" : "warning"}>{r.status}</Badge>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<Pagination page={page} totalPages={5} onPageChange={setPage} />
</Stack>
);
} <script setup lang="ts">
import { ref } from "vue";
import {
Stack, Heading, Input, Button, Table, TableHead, TableBody, TableRow,
TableHeader, TableCell, Badge, Pagination,
} from "@usevyre/vue";
const page = ref(1);
const query = ref("");
const rows = [
{ name: "Acme Inc", plan: "Pro", status: "Active" },
{ name: "Globex", plan: "Starter", status: "Trial" },
{ name: "Initech", plan: "Business", status: "Active" },
];
</script>
<template>
<Stack direction="column" gap="md">
<Stack direction="row" align="center" justify="between">
<Heading size="lg">Customers</Heading>
<Stack direction="row" gap="sm">
<Input v-model="query" placeholder="Search…" />
<Button variant="accent">Add customer</Button>
</Stack>
</Stack>
<Table>
<TableHead>
<TableRow>
<TableHeader>Name</TableHeader>
<TableHeader>Plan</TableHeader>
<TableHeader>Status</TableHeader>
</TableRow>
</TableHead>
<TableBody>
<TableRow v-for="r in rows" :key="r.name">
<TableCell>{{ r.name }}</TableCell>
<TableCell>{{ r.plan }}</TableCell>
<TableCell>
<Badge :variant="r.status === 'Active' ? 'success' : 'warning'">{{ r.status }}</Badge>
</TableCell>
</TableRow>
</TableBody>
</Table>
<Pagination :page="page" :total-pages="5" @page-change="page = $event" />
</Stack>
</template> Form page
A validated create/edit form in a card with submit and cancel actions.
New member
import { useState } from "react";
import { Card, CardBody, Heading, Form, FormField, Input, Button, Stack } from "@usevyre/react";
export function FormPage() {
const [values, setValues] = useState({ name: "", email: "", role: "" });
return (
<Card style={{ width: "100%", maxWidth: 480 }}>
<CardBody>
<Stack direction="column" gap="lg">
<Heading size="lg">New member</Heading>
<Form values={values} onChange={(v) => setValues(v as typeof values)} onSubmit={() => {}}>
<FormField name="name" label="Full name" rules={{ required: true }}>
<Input placeholder="Ada Lovelace" />
</FormField>
<FormField name="email" label="Email" rules={{ required: true, email: true }}>
<Input type="email" placeholder="ada@example.com" />
</FormField>
<FormField name="role" label="Role">
<Input placeholder="Engineer" />
</FormField>
<Stack direction="row" gap="sm" justify="end">
<Button variant="ghost" type="button">Cancel</Button>
<Button variant="accent" type="submit">Create</Button>
</Stack>
</Form>
</Stack>
</CardBody>
</Card>
);
} <script setup lang="ts">
import { reactive } from "vue";
import { Card, CardBody, Heading, Form, FormField, Input, Button, Stack } from "@usevyre/vue";
const values = reactive({ name: "", email: "", role: "" });
</script>
<template>
<Card :style="{ width: '100%', maxWidth: '480px' }">
<CardBody>
<Stack direction="column" gap="lg">
<Heading size="lg">New member</Heading>
<Form :values="values" @submit="() => {}">
<FormField name="name" label="Full name" :rules="{ required: true }">
<Input placeholder="Ada Lovelace" />
</FormField>
<FormField name="email" label="Email" :rules="{ required: true, email: true }">
<Input type="email" placeholder="ada@example.com" />
</FormField>
<FormField name="role" label="Role">
<Input placeholder="Engineer" />
</FormField>
<Stack direction="row" gap="sm" justify="end">
<Button variant="ghost" type="button">Cancel</Button>
<Button variant="accent" type="submit">Create</Button>
</Stack>
</Form>
</Stack>
</CardBody>
</Card>
</template> Item list
A list of rows with media, title/description and a trailing action.
ada@example.com
alan@example.com
grace@example.com
import {
Card, CardBody, ItemGroup, Item, ItemMedia, ItemContent, ItemTitle,
ItemDescription, ItemActions, Avatar, Button,
} from "@usevyre/react";
const members = [
{ name: "Ada Lovelace", email: "ada@example.com", initials: "AL" },
{ name: "Alan Turing", email: "alan@example.com", initials: "AT" },
{ name: "Grace Hopper", email: "grace@example.com", initials: "GH" },
];
export function ItemList() {
return (
<Card style={{ width: "100%" }}>
<CardBody>
<ItemGroup>
{members.map((m) => (
<Item key={m.email}>
<ItemMedia><Avatar fallback={m.initials} size="sm" /></ItemMedia>
<ItemContent>
<ItemTitle>{m.name}</ItemTitle>
<ItemDescription>{m.email}</ItemDescription>
</ItemContent>
<ItemActions><Button variant="ghost" size="sm">Manage</Button></ItemActions>
</Item>
))}
</ItemGroup>
</CardBody>
</Card>
);
} <script setup lang="ts">
import {
Card, CardBody, ItemGroup, Item, ItemMedia, ItemContent, ItemTitle,
ItemDescription, ItemActions, Avatar, Button,
} from "@usevyre/vue";
const members = [
{ name: "Ada Lovelace", email: "ada@example.com", initials: "AL" },
{ name: "Alan Turing", email: "alan@example.com", initials: "AT" },
{ name: "Grace Hopper", email: "grace@example.com", initials: "GH" },
];
</script>
<template>
<Card :style="{ width: '100%' }">
<CardBody>
<ItemGroup>
<Item v-for="m in members" :key="m.email">
<ItemMedia><Avatar :fallback="m.initials" size="sm" /></ItemMedia>
<ItemContent>
<ItemTitle>{{ m.name }}</ItemTitle>
<ItemDescription>{{ m.email }}</ItemDescription>
</ItemContent>
<ItemActions><Button variant="ghost" size="sm">Manage</Button></ItemActions>
</Item>
</ItemGroup>
</CardBody>
</Card>
</template> Confirm dialog
A destructive-action confirmation modal. Click to open.
import { useState } from "react";
import { Modal, ModalHeader, ModalBody, ModalFooter, Button } from "@usevyre/react";
export function ConfirmDialog() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="danger" onClick={() => setOpen(true)}>Delete project</Button>
<Modal open={open} onClose={() => setOpen(false)} size="sm">
<ModalHeader>Delete project?</ModalHeader>
<ModalBody>This permanently removes the project and all its data. This action cannot be undone.</ModalBody>
<ModalFooter>
<Button variant="ghost" onClick={() => setOpen(false)}>Cancel</Button>
<Button variant="danger" onClick={() => setOpen(false)}>Delete</Button>
</ModalFooter>
</Modal>
</>
);
} <script setup lang="ts">
import { ref } from "vue";
import { Modal, ModalHeader, ModalBody, ModalFooter, Button } from "@usevyre/vue";
const open = ref(false);
</script>
<template>
<Button variant="danger" @click="open = true">Delete project</Button>
<Modal :open="open" size="sm" @close="open = false">
<ModalHeader>Delete project?</ModalHeader>
<ModalBody>This permanently removes the project and all its data. This action cannot be undone.</ModalBody>
<ModalFooter>
<Button variant="ghost" @click="open = false">Cancel</Button>
<Button variant="danger" @click="open = false">Delete</Button>
</ModalFooter>
</Modal>
</template>