Start from the Vite starter or copy a Next.js settings panel and destructive confirmation recipe.
The repository ships a compact Vite + React 19 + TypeScript starter that already imports the package stylesheet and renders a landing page with a live workspace preview, three feature cards, a light/dark toggle, and a call to action.
pnpm dlx degit xingxingmofashu/ui/templates/starter my-app
cd my-app
pnpm install
pnpm devThe starter pins a released package version. After cloning, bump
@xingxingmofashu/ui in package.json when you want a newer release.
Useful files:
src/index.css imports Tailwind CSS, the package stylesheet, and the glass
preset stylesheet, then draws the page canvas.src/App.tsx contains the hero copy, feature cards, CTA, and footer.src/hero-preview.tsx contains the interactive preview card. Its material
switch uses GlassPresetScope, so it only affects that card. Wrap the app in
GlassPresetProvider when you want a global, persisted preset.src/use-theme.ts persists the light/dark preference.vite.config.ts contains the Tailwind and React plugins.The preview uses Tabs, Switch, Progress, ToggleGroup, Button, and
Toast. Replace its content with the components your product needs.
Install the package in an existing Next.js 16 project:
pnpm add @xingxingmofashu/uiImport the stylesheet in the global CSS file:
@import "tailwindcss";
@import "@xingxingmofashu/ui";Render a server-safe Card from a Server Component:
import { Card } from "@xingxingmofashu/ui/components/card";
export default function Page() {
return (
<main className="mx-auto max-w-3xl p-8">
<Card title="Liquid glass" description="Rendered on the server.">
<p className="text-sm text-ui-ink/70">
Add client components inside the card when you need interaction.
</p>
</Card>
</main>
);
}Add "use client" to the file that owns state or event handlers. See
Server rendering for the boundary rules.
A complete settings panel with a card, switches, and a save action. The buttons and switches need client state, so the whole panel is a client component.
"use client";
import { useState } from "react";
import { Button, Card, Switch } from "@xingxingmofashu/ui";
export function SettingsPanel() {
const [saved, setSaved] = useState(false);
return (
<Card
className="w-full max-w-lg"
title="Notifications"
description="Choose what your workspace sends to your inbox."
footer={
<Button
size="sm"
label={saved ? "Saved" : "Save changes"}
onClick={() => setSaved(true)}
/>
}
>
<div className="flex flex-col gap-5">
<Switch
label="Email notifications"
description="Send a summary when a teammate publishes changes."
defaultChecked
/>
<Switch
label="Weekly digest"
description="Email the team every Monday morning."
/>
<Switch
label="Mentions"
description="Notify you when someone mentions your name."
defaultChecked
/>
</div>
</Card>
);
}AlertDialog is built for irreversible actions. The confirm and cancel buttons
close the dialog automatically; use the callbacks for the side effect. Escape
also closes it without invoking either callback, so guard the destructive action
in onOpenChange when that matters.
"use client";
import { Trash2 } from "lucide-react";
import { useState } from "react";
import { AlertDialog, Button } from "@xingxingmofashu/ui";
export function DeleteProject() {
const [open, setOpen] = useState(false);
return (
<>
<Button
label="Delete project"
leadingIcon={<Trash2 />}
onClick={() => setOpen(true)}
/>
<AlertDialog
open={open}
onOpenChange={setOpen}
title="Delete project?"
description="This action cannot be undone. All files and deployments will be removed."
confirmLabel="Delete project"
cancelLabel="Cancel"
onConfirm={() => {
// Call your delete endpoint here.
}}
/>
</>
);
}Use Drawer instead of Dialog when the panel should slide in from an edge.
The footer buttons do not close the drawer automatically, so update open
yourself.
"use client";
import { Settings } from "lucide-react";
import { useState } from "react";
import { Button, Drawer, Switch } from "@xingxingmofashu/ui";
export function MobileSettings() {
const [open, setOpen] = useState(false);
return (
<>
<Button
label="Settings"
leadingIcon={<Settings />}
onClick={() => setOpen(true)}
/>
<Drawer
open={open}
onOpenChange={setOpen}
title="Settings"
description="Changes apply to this device."
footer={
<>
<Button size="sm" label="Cancel" onClick={() => setOpen(false)} />
<Button size="sm" label="Done" onClick={() => setOpen(false)} />
</>
}
>
<Switch label="Notifications" defaultChecked />
</Drawer>
</>
);
}