Files
Denozordec b1467575c5
Build, Test, and Push CFDM Docker Image / test (push) Failing after 3h13m2s
Build, Test, and Push CFDM Docker Image / create-release (push) Has been cancelled
Build, Test, and Push CFDM Docker Image / build-and-push (push) Has been cancelled
Build, Test, and Push CFDM Docker Image / update-wiki (push) Has been cancelled
Refactor project structure to use pnpm monorepo; update Dockerfile and related configurations for frontend build process. Adjust .dockerignore and .gitignore to reflect new paths. Modify .env.example for cron job timing. Update CONTRIBUTING.md and README.md for new development instructions.
2026-06-15 15:37:36 +07:00

4.2 KiB

Component Composition

Contents

  • Items always inside their Group component
  • Callouts use Alert
  • Empty states use Empty component
  • Toast notifications use sonner
  • Choosing between overlay components
  • Dialog, Sheet, and Drawer always need a Title
  • Card structure
  • Button has no isPending or isLoading prop
  • TabsTrigger must be inside TabsList
  • Avatar always needs AvatarFallback
  • Use Separator instead of raw hr or border divs
  • Use Skeleton for loading placeholders
  • Use Badge instead of custom styled spans

Items always inside their Group component

Never render items directly inside the content container.

Incorrect:

<SelectContent>
  <SelectItem value="apple">Apple</SelectItem>
  <SelectItem value="banana">Banana</SelectItem>
</SelectContent>

Correct:

<SelectContent>
  <SelectGroup>
    <SelectItem value="apple">Apple</SelectItem>
    <SelectItem value="banana">Banana</SelectItem>
  </SelectGroup>
</SelectContent>

This applies to all group-based components:

Item Group
SelectItem, SelectLabel SelectGroup
DropdownMenuItem, DropdownMenuLabel, DropdownMenuSub DropdownMenuGroup
MenubarItem MenubarGroup
ContextMenuItem ContextMenuGroup
CommandItem CommandGroup

Callouts use Alert

<Alert>
  <AlertTitle>Warning</AlertTitle>
  <AlertDescription>Something needs attention.</AlertDescription>
</Alert>

Empty states use Empty component

<Empty>
  <EmptyHeader>
    <EmptyMedia variant="icon"><FolderIcon /></EmptyMedia>
    <EmptyTitle>No projects yet</EmptyTitle>
    <EmptyDescription>Get started by creating a new project.</EmptyDescription>
  </EmptyHeader>
  <EmptyContent>
    <Button>Create Project</Button>
  </EmptyContent>
</Empty>

Toast notifications use sonner

import { toast } from "sonner"

toast.success("Changes saved.")
toast.error("Something went wrong.")
toast("File deleted.", {
  action: { label: "Undo", onClick: () => undoDelete() },
})

Choosing between overlay components

Use case Component
Focused task that requires input Dialog
Destructive action confirmation AlertDialog
Side panel with details or filters Sheet
Mobile-first bottom panel Drawer
Quick info on hover HoverCard
Small contextual content on click Popover

Dialog, Sheet, and Drawer always need a Title

DialogTitle, SheetTitle, DrawerTitle are required for accessibility. Use className="sr-only" if visually hidden.

<DialogContent>
  <DialogHeader>
    <DialogTitle>Edit Profile</DialogTitle>
    <DialogDescription>Update your profile.</DialogDescription>
  </DialogHeader>
  ...
</DialogContent>

Card structure

Use full composition — don't dump everything into CardContent:

<Card>
  <CardHeader>
    <CardTitle>Team Members</CardTitle>
    <CardDescription>Manage your team.</CardDescription>
  </CardHeader>
  <CardContent>...</CardContent>
  <CardFooter>
    <Button>Invite</Button>
  </CardFooter>
</Card>

Button has no isPending or isLoading prop

Compose with Spinner + data-icon + disabled:

<Button disabled>
  <Spinner data-icon="inline-start" />
  Saving...
</Button>

TabsTrigger must be inside TabsList

Never render TabsTrigger directly inside Tabs — always wrap in TabsList:

<Tabs defaultValue="account">
  <TabsList>
    <TabsTrigger value="account">Account</TabsTrigger>
    <TabsTrigger value="password">Password</TabsTrigger>
  </TabsList>
  <TabsContent value="account">...</TabsContent>
</Tabs>

Avatar always needs AvatarFallback

Always include AvatarFallback for when the image fails to load:

<Avatar>
  <AvatarImage src="/avatar.png" alt="User" />
  <AvatarFallback>JD</AvatarFallback>
</Avatar>

Use existing components instead of custom markup

Instead of Use
<hr> or <div className="border-t"> <Separator />
<div className="animate-pulse"> with styled divs <Skeleton className="h-4 w-3/4" />
<span className="rounded-full bg-green-100 ..."> <Badge variant="secondary">