diff --git a/skills/shadcn/SKILL.md b/skills/shadcn/SKILL.md index dcdb754588..8c01af9295 100644 --- a/skills/shadcn/SKILL.md +++ b/skills/shadcn/SKILL.md @@ -1,6 +1,6 @@ --- name: shadcn -description: Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for "shadcn init", "create an app with --preset", or "switch to --preset". +description: Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI, including chat interfaces. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for "shadcn init", "create an app with --preset", or "switch to --preset". user-invocable: false allowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *) --- @@ -75,6 +75,12 @@ These rules are **always enforced**. Each links to a file with Incorrect/Correct - **No sizing classes on icons inside components.** Components handle icon sizing via CSS. No `size-4` or `w-4 h-4`. - **Pass icons as objects, not string keys.** `icon={CheckIcon}`, not a string lookup. +### Chat & Messaging → [chat.md](./rules/chat.md) + +- **Chat UI composes the chat primitives.** Conversations use `MessageScroller`, rows use `Message`, surfaces use `Bubble`. Never hand-rolled bubble `div`s or a raw scroll container. +- **`MessageScroller` owns scroll behavior.** Streaming follow, anchoring, and jump-to-latest (`MessageScrollerButton`) are built in. Don't write a `useStickToBottom`/`ResizeObserver` hook. +- **Attachments use `Attachment`; system notes and dividers use `Marker`.** Not `Item` cards or `Separator` + a label. + ### CLI - **Never decode preset codes or build preset URLs manually.** Use `npx shadcn@latest preset decode `, `preset url `, or `preset open `. For project-aware preset detection, use `npx shadcn@latest preset resolve`. @@ -136,6 +142,7 @@ These are the most common patterns that differentiate correct shadcn/ui code. Fo | Empty states | `Empty` | | Menus | `DropdownMenu`, `ContextMenu`, `Menubar` | | Tooltips/info | `Tooltip`, `HoverCard`, `Popover` | +| Chat / conversation UI | `MessageScroller`, `Message`, `Bubble`, `Attachment`, `Marker` | ## Key Fields @@ -259,6 +266,7 @@ npx shadcn@latest view owner/repo/item - [rules/forms.md](./rules/forms.md) — FieldGroup, Field, InputGroup, ToggleGroup, FieldSet, validation states - [rules/composition.md](./rules/composition.md) — Groups, overlays, Card, Tabs, Avatar, Alert, Empty, Toast, Separator, Skeleton, Badge, Button loading +- [rules/chat.md](./rules/chat.md) — MessageScroller, Message, Bubble, Attachment, Marker; streaming, anchoring, jump-to-latest - [rules/icons.md](./rules/icons.md) — data-icon, icon sizing, passing icons as objects - [rules/styling.md](./rules/styling.md) — Semantic colors, variants, className, spacing, size, truncate, dark mode, cn(), z-index - [rules/base-vs-radix.md](./rules/base-vs-radix.md) — asChild vs render, Select, ToggleGroup, Slider, Accordion diff --git a/skills/shadcn/evals/evals.json b/skills/shadcn/evals/evals.json index 0df77e4b9b..c1b7bdd310 100644 --- a/skills/shadcn/evals/evals.json +++ b/skills/shadcn/evals/evals.json @@ -42,6 +42,36 @@ "Uses gap-* instead of space-y-* or space-x-* for spacing", "Uses size-* when width and height are equal instead of separate w-* h-*" ] + }, + { + "id": 4, + "prompt": "I'm building a Next.js app with shadcn/ui (base-nova preset, lucide icons). Build a chat conversation view: a scrollable thread of messages from two different people, each with an avatar, sender name, timestamp, and message bubble. A couple of messages include an image attachment and a PDF file attachment, and there's a 'Today' divider separating the days.", + "expected_output": "A React component composing MessageScroller, Message, Bubble, Attachment, and Marker from the registry instead of hand-rolled bubble/divider/attachment markup.", + "files": [], + "expectations": [ + "Uses MessageScroller (MessageScrollerProvider, MessageScrollerViewport, MessageScrollerContent, MessageScrollerItem) for the scrollable thread instead of a raw overflow-y-auto div or ScrollArea", + "Wraps each row in MessageScrollerItem inside MessageScrollerContent", + "Uses Message with MessageAvatar/MessageContent/MessageHeader for row layout instead of custom flex divs", + "Uses Bubble + BubbleContent for the message surface instead of a styled div with bg-muted/bg-primary", + "Uses Attachment (AttachmentMedia, AttachmentContent, AttachmentTitle, AttachmentDescription) for the file and image attachments instead of Item or a custom card", + "Uses Marker (variant=\"separator\") for the 'Today' divider instead of Separator plus a centered label", + "Uses semantic color tokens and gap-* spacing; no raw colors like bg-emerald-500 and no space-y-*", + "Includes \"use client\" when the component uses state or event handlers (isRSC)" + ] + }, + { + "id": 5, + "prompt": "Using shadcn/ui (base-nova preset, lucide icons), build a streaming AI chat UI. The assistant's reply streams in while it generates, the view auto-scrolls to follow the latest content but stops following if the user scrolls up to read earlier messages, a 'jump to latest' button appears when the user has scrolled away from the bottom, and a subtle 'thinking…' shimmer shows while the model is generating.", + "expected_output": "A React component that delegates scroll/anchor behavior to MessageScroller and uses MessageScrollerButton for jump-to-latest and the shimmer utility for the thinking indicator — no hand-rolled scroll logic or custom shimmer keyframes.", + "files": [], + "expectations": [ + "Uses MessageScroller with MessageScrollerProvider (autoScroll) and scrollAnchor on message items for the stick-to-bottom/follow behavior instead of a custom useStickToBottom hook or ResizeObserver/scrollTop wiring", + "Uses MessageScrollerButton for the jump-to-latest control instead of a hand-built conditional button driven by manual scroll-position state", + "Uses the shimmer utility class for the 'thinking…' indicator instead of a custom @keyframes or bg-clip-text gradient animation", + "Wraps each message row in MessageScrollerItem inside MessageScrollerContent", + "Uses Message + Bubble + BubbleContent for the conversation rows instead of hand-rolled bubble divs", + "Uses semantic color tokens and gap-* spacing; includes \"use client\" (isRSC)" + ] } ] } diff --git a/skills/shadcn/rules/chat.md b/skills/shadcn/rules/chat.md new file mode 100644 index 0000000000..8ce62fe4bf --- /dev/null +++ b/skills/shadcn/rules/chat.md @@ -0,0 +1,224 @@ +# Chat & Messaging + +Components for conversation and chat UI. Compose these instead of hand-rolling +bubbles, scroll containers, dividers, or attachment cards. + +Install: `npx shadcn@latest add message-scroller message bubble attachment marker` + +The same component names and props ship for both `base` and `radix`; only +composition differs (`render` vs `asChild`). See [base-vs-radix.md](./base-vs-radix.md). + +## Contents + +- Scrollable threads use MessageScroller +- Message rows use Message +- Message surfaces use Bubble +- Attachments use Attachment +- System notes and dividers use Marker +- Streaming, anchoring, and jump-to-latest are built in +- Escape hatch: the scroller hooks + +--- + +## Scrollable threads use MessageScroller + +A conversation that scrolls, follows new messages, restores position, or jumps +to a message uses `MessageScroller`. Don't build a raw overflow container with +manual scroll wiring, and don't reach for `ScrollArea`. + +The parts nest in a fixed order. Every direct child of the content is wrapped in +a `MessageScrollerItem` so the scroller can measure, anchor, preserve position, +track visibility, and jump to it. `MessageScrollerButton` sits inside +`MessageScroller`, after the viewport. + +**Incorrect:** + +```tsx +// Hand-rolled scroll container with manual stick-to-bottom logic. +
+
+ {messages.map((m) => ( + + ))} +
+
+``` + +**Correct:** + +```tsx + + + + + {messages.map((message) => ( + + + {/* ...message content... */} + + + ))} + + + + + +``` + +--- + +## Message rows use Message + +`Message` lays out a single row: avatar, header, content, footer, with +alignment. Group consecutive rows from one sender with `MessageGroup`. Don't +rebuild the row from flex divs. + +`align="end"` is the current user's side; `align="start"` is everyone else. + +```tsx + + + + + {initials} + + + + {sender.name} + + {text} + + {time} + + +``` + +--- + +## Message surfaces use Bubble + +The colored message surface is `Bubble` + `BubbleContent`, never a styled `div` +with `bg-muted` / `bg-primary` and hand-managed corners. + +- `variant`: `default`, `secondary`, `muted`, `tinted`, `outline`, `ghost`, `destructive`. +- `align`: `start` or `end` (matches the `Message` side). + +`BubbleReactions` renders the reaction cluster. `side` (`top` | `bottom`) and +`align` (`start` | `end`) position it against the bubble. Don't lay reactions out +with absolutely-positioned `Badge`s. + +**Incorrect:** + +```tsx +
+ {text} +
+``` + +**Correct:** + +```tsx + + {text} + + 👍 2 + + +``` + +--- + +## Attachments use Attachment + +File and image attachments use `Attachment`, not `Item` or a custom card. It +carries upload state, so wire `state` to the real status rather than rendering a +separate spinner. + +- `state`: `idle`, `uploading`, `processing`, `error`, `done`. `uploading` and + `processing` apply the `shimmer` animation to the title automatically. +- `size`: `default`, `sm`, `xs`. `orientation`: `horizontal`, `vertical`. +- Use `AttachmentGroup` to lay out several attachments in a scrolling row. + +```tsx + + + + + + homepage-feedback.pdf + PDF · 2.4 MB + + + + + + + +``` + +For an image, use `` with an `img` child. + +--- + +## System notes and dividers use Marker + +Status lines ("Sarah joined the conversation"), date dividers ("Today"), and +labeled separators are `Marker`, not a `Separator` plus a centered span. + +- `variant`: `default` (plain row), `separator` (centered label with rules on + each side), `border` (bottom-bordered row). +- `MarkerIcon` holds a leading icon; `MarkerContent` holds the label. + +**Incorrect:** + +```tsx +
+ + Today + +
+``` + +**Correct:** + +```tsx + + Today + +``` + +--- + +## Streaming, anchoring, and jump-to-latest are built in + +`MessageScroller` handles the behavior that chat UIs usually reinvent. Don't +write a `useStickToBottom` hook, a `ResizeObserver`, or manual `scrollTop` math. + +- **Follow the live edge while streaming.** `MessageScrollerProvider` with + `autoScroll` keeps the view pinned to new content and yields the moment the + user scrolls up. Streaming token updates that grow the last message are + followed automatically. +- **Anchor a turn.** `scrollAnchor` on a `MessageScrollerItem` marks the row to + hold in view (typically the user's message that started the turn). +- **Jump to latest.** `MessageScrollerButton` appears when the user scrolls away + and scrolls back on click. `direction="end"` (default) or `direction="start"`. + It is a self-managing control, so don't gate it behind your own scroll-position + state. + +For a "thinking…" indicator while the model generates, apply the `shimmer` +utility to text. Don't author a custom keyframe animation. See +[styling.md](./styling.md). + +--- + +## Escape hatch: the scroller hooks + +For behavior the parts don't expose, read state from the hooks rather than +re-implementing the scroller: `useMessageScroller`, +`useMessageScrollerVisibility`, and `useMessageScrollerScrollable`. They come +from the auto-installed `@shadcn/react` dependency, so there's nothing extra to +install. Reach for them only when composition can't express what you need. diff --git a/skills/shadcn/rules/composition.md b/skills/shadcn/rules/composition.md index 0e105837cb..0654245aba 100644 --- a/skills/shadcn/rules/composition.md +++ b/skills/shadcn/rules/composition.md @@ -51,6 +51,12 @@ This applies to all group-based components: | `MenubarItem` | `MenubarGroup` | | `ContextMenuItem` | `ContextMenuGroup` | | `CommandItem` | `CommandGroup` | +| `MessageScrollerItem` | `MessageScrollerContent` | +| `Message` (consecutive, same sender) | `MessageGroup` | +| `Bubble` (stacked) | `BubbleGroup` | +| `Attachment` (in a row) | `AttachmentGroup` | + +Chat components nest in a fixed order (`MessageScrollerProvider` → `MessageScroller` → `MessageScrollerViewport` → `MessageScrollerContent` → `MessageScrollerItem`). See [chat.md](./chat.md). --- diff --git a/skills/shadcn/rules/styling.md b/skills/shadcn/rules/styling.md index 38d473212b..fe75197968 100644 --- a/skills/shadcn/rules/styling.md +++ b/skills/shadcn/rules/styling.md @@ -13,6 +13,7 @@ See [customization.md](../customization.md) for theming, CSS variables, and addi - No manual dark: color overrides - Use cn() for conditional classes - No manual z-index on overlay components +- Use shimmer / scroll-fade utilities, not custom animations --- @@ -160,3 +161,25 @@ import { cn } from "@/lib/utils" ## No manual z-index on overlay components `Dialog`, `Sheet`, `Drawer`, `AlertDialog`, `DropdownMenu`, `Popover`, `Tooltip`, `HoverCard` handle their own stacking. Never add `z-50` or `z-[999]`. + +--- + +## Use shimmer / scroll-fade utilities, not custom animations + +For a live "thinking…" or loading-text shimmer, apply the `shimmer` utility. Don't author a custom `@keyframes` or a `bg-clip-text` gradient sweep. + +For scroll-aware edge fading on a scroll container, use `scroll-fade` (and the axis variants `scroll-fade-x` / `scroll-fade-b`). Don't hand-roll mask gradients. The chat components already apply these internally: `Attachment` shimmers its title during upload, and `MessageScrollerViewport` fades its edges. + +**Incorrect:** + +```tsx + + Thinking… + +``` + +**Correct:** + +```tsx +Thinking… +```