feat: add new OpenSpec skills for change management and onboarding
- Created `openspec-ff-change` skill for fast-forward artifact creation. - Introduced `openspec-new-change` skill for structured change creation. - Developed `openspec-onboard` skill for guided onboarding through OpenSpec workflow. - Added `openspec-sync-specs` skill for syncing delta specs to main specs. - Implemented `openspec-verify-change` skill for verifying implementation against change artifacts. - Updated `.gitignore` to exclude OpenSpec generated files. - Added `skills-lock.json` to manage skill dependencies.
This commit is contained in:
88
.agents/skills/astro/SKILL.md
Normal file
88
.agents/skills/astro/SKILL.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
---
|
||||||
|
name: astro
|
||||||
|
description: Skill for using Astro projects. Includes CLI commands, project structure, core config options, and adapters. Use this skill when the user needs to work with Astro or when the user mentions Astro.
|
||||||
|
license: MIT
|
||||||
|
metadata:
|
||||||
|
authors: "Astro Team"
|
||||||
|
version: "0.0.1"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Astro Usage Guide
|
||||||
|
|
||||||
|
**Always consult [docs.astro.build](https://docs.astro.build) for code examples and latest API.**
|
||||||
|
|
||||||
|
Astro is the web framework for content-driven websites.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
### File Location
|
||||||
|
CLI looks for `astro.config.js`, `astro.config.mjs`, `astro.config.cjs`, and `astro.config.ts` in: `./`. Use `--config` for custom path.
|
||||||
|
|
||||||
|
### CLI Commands
|
||||||
|
|
||||||
|
- `npx astro dev` - Start the development server.
|
||||||
|
- `npx astro build` - Build your project and write it to disk.
|
||||||
|
- `npx astro check` - Check your project for errors.
|
||||||
|
- `npx astro add` - Add an integration.
|
||||||
|
- `npmx astro sync` - Generate TypeScript types for all Astro modules.
|
||||||
|
|
||||||
|
**Re-run after adding/changing plugins.**
|
||||||
|
|
||||||
|
### Project Structure
|
||||||
|
|
||||||
|
Astro leverages an opinionated folder layout for your project. Every Astro project root should include some directories and files. Reference [project structure docs](https://docs.astro.build/en/basics/project-structure).
|
||||||
|
|
||||||
|
- `src/*` - Your project source code (components, pages, styles, images, etc.)
|
||||||
|
- `src/pages` - Required sub-directory in your Astro project. Without it, your site will have no pages or routes!
|
||||||
|
- `src/components` - It is common to group and organize all of your project components together in this folder. This is a common convention in Astro projects, but it is not required. Feel free to organize your components however you like!
|
||||||
|
- `src/layouts` - Just like `src/components`, this directory is a common convention but not required.
|
||||||
|
- `src/styles` - It is a common convention to store your CSS or Sass files here, but this is not required. As long as your styles live somewhere in the src/ directory and are imported correctly, Astro will handle and optimize them.
|
||||||
|
- `public/*` - Your non-code, unprocessed assets (fonts, icons, etc.). The files in this folder will be copied into the build folder untouched, and then your site will be built.
|
||||||
|
- `package.json` - A project manifest.
|
||||||
|
- `astro.config.{js,mjs,cjs,ts}` - An Astro configuration file. (recommended)
|
||||||
|
- `tsconfig.json` - A TypeScript configuration file. (recommended)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Config Options
|
||||||
|
|
||||||
|
| Option | Notes |
|
||||||
|
|--------|-------|
|
||||||
|
| `site` | Your final, deployed URL. Astro uses this full URL to generate your sitemap and canonical URLs in your final build. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Adapters
|
||||||
|
|
||||||
|
Deploy to your favorite server, serverless, or edge host with build adapters. Use an adapter to enable on-demand rendering in your Astro project.
|
||||||
|
|
||||||
|
**Add [Node.js](https://docs.astro.build/en/guides/integrations-guide/node) adapter using astro add:**
|
||||||
|
```
|
||||||
|
npx astro add node --yes
|
||||||
|
```
|
||||||
|
|
||||||
|
**Add [Cloudflare](https://docs.astro.build/en/guides/integrations-guide/cloudflare) adapter using astro add:**
|
||||||
|
```
|
||||||
|
npx astro add cloudflare --yes
|
||||||
|
```
|
||||||
|
|
||||||
|
**Add [Netlify](https://docs.astro.build/en/guides/integrations-guide/netlify) adapter using astro add:**
|
||||||
|
```
|
||||||
|
npx astro add netlify --yes
|
||||||
|
```
|
||||||
|
|
||||||
|
**Add [Vercel](https://docs.astro.build/en/guides/integrations-guide/vercel) adapter using astro add:**
|
||||||
|
```
|
||||||
|
npx astro add vercel --yes
|
||||||
|
```
|
||||||
|
|
||||||
|
[Other Community adapters](https://astro.build/integrations/2/?search=&categories%5B%5D=adapters)
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [Docs](https://docs.astro.build)
|
||||||
|
- [Config Reference](https://docs.astro.build/en/reference/configuration-reference/)
|
||||||
|
- [llms.txt](https://docs.astro.build/llms.txt)
|
||||||
|
- [GitHub](https://github.com/withastro/astro)
|
||||||
205
.agents/skills/framer-motion/SKILL.md
Normal file
205
.agents/skills/framer-motion/SKILL.md
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
---
|
||||||
|
name: framer-motion
|
||||||
|
description: Expert guidelines for building performant animations with Framer Motion/Motion library in React applications
|
||||||
|
---
|
||||||
|
|
||||||
|
# Framer Motion / Motion Animation Guidelines
|
||||||
|
|
||||||
|
You are an expert in Framer Motion (now Motion), React, and TypeScript. Follow these guidelines when creating animations.
|
||||||
|
|
||||||
|
## Core Principles
|
||||||
|
|
||||||
|
### Import from the Correct Package
|
||||||
|
- Use `import { motion } from "motion/react"` for React projects (not "framer-motion" - this is outdated)
|
||||||
|
- The library was renamed from Framer Motion to Motion
|
||||||
|
- Always use the latest Motion API
|
||||||
|
|
||||||
|
### Performance-First Approach
|
||||||
|
- Animate transform properties (`x`, `y`, `scale`, `rotate`) and `opacity` for best performance
|
||||||
|
- These properties can be hardware-accelerated and don't trigger layout recalculations
|
||||||
|
- Avoid animating properties that cause layout shifts like `width`, `height`, `top`, `left`, `margin`, `padding`
|
||||||
|
|
||||||
|
## Hardware Acceleration
|
||||||
|
|
||||||
|
### Use will-change Properly
|
||||||
|
```tsx
|
||||||
|
// When animating transforms
|
||||||
|
<motion.div
|
||||||
|
style={{ willChange: "transform" }}
|
||||||
|
animate={{ x: 100, y: 50, scale: 1.2 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
// When animating other GPU-accelerated properties
|
||||||
|
<motion.div
|
||||||
|
style={{ willChange: "opacity, transform" }}
|
||||||
|
animate={{ opacity: 0.5, x: 100 }}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Properties to Add to willChange
|
||||||
|
- `transform` - for x, y, scale, rotate, skew
|
||||||
|
- `opacity` - for opacity animations
|
||||||
|
- `filter` - for blur, brightness, etc.
|
||||||
|
- `clipPath` - for clip-path animations
|
||||||
|
- `backgroundColor` - for background color transitions
|
||||||
|
|
||||||
|
## Animation Best Practices
|
||||||
|
|
||||||
|
### Use Variants for Complex Animations
|
||||||
|
```tsx
|
||||||
|
const containerVariants = {
|
||||||
|
hidden: { opacity: 0 },
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
transition: {
|
||||||
|
staggerChildren: 0.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const itemVariants = {
|
||||||
|
hidden: { y: 20, opacity: 0 },
|
||||||
|
visible: { y: 0, opacity: 1 }
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use layoutId for Shared Element Transitions
|
||||||
|
```tsx
|
||||||
|
<motion.div layoutId="shared-element" />
|
||||||
|
```
|
||||||
|
|
||||||
|
### Prefer spring Animations
|
||||||
|
```tsx
|
||||||
|
// Springs feel more natural than duration-based animations
|
||||||
|
<motion.div
|
||||||
|
animate={{ x: 100 }}
|
||||||
|
transition={{ type: "spring", stiffness: 300, damping: 30 }}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
## React Integration
|
||||||
|
|
||||||
|
### Memoization for Performance
|
||||||
|
```tsx
|
||||||
|
// Memoize animation variants
|
||||||
|
const variants = useMemo(() => ({
|
||||||
|
hidden: { opacity: 0 },
|
||||||
|
visible: { opacity: 1 }
|
||||||
|
}), []);
|
||||||
|
|
||||||
|
// Memoize callbacks
|
||||||
|
const handleAnimationComplete = useCallback(() => {
|
||||||
|
// handler logic
|
||||||
|
}, []);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Avoid Inline Style Objects
|
||||||
|
```tsx
|
||||||
|
// Bad - creates new object on every render
|
||||||
|
<motion.div style={{ willChange: "transform" }} />
|
||||||
|
|
||||||
|
// Good - define outside or memoize
|
||||||
|
const style = { willChange: "transform" };
|
||||||
|
<motion.div style={style} />
|
||||||
|
```
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
### Respect Reduced Motion Preferences
|
||||||
|
```tsx
|
||||||
|
import { useReducedMotion } from "motion/react";
|
||||||
|
|
||||||
|
function Component() {
|
||||||
|
const shouldReduceMotion = useReducedMotion();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
animate={{ x: shouldReduceMotion ? 0 : 100 }}
|
||||||
|
transition={{ duration: shouldReduceMotion ? 0 : 0.3 }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gesture Animations
|
||||||
|
|
||||||
|
### Use Gesture Props Correctly
|
||||||
|
```tsx
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
|
transition={{ type: "spring", stiffness: 400, damping: 17 }}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Scroll Animations
|
||||||
|
|
||||||
|
### Use useScroll for Scroll-Linked Animations
|
||||||
|
```tsx
|
||||||
|
import { useScroll, useTransform, motion } from "motion/react";
|
||||||
|
|
||||||
|
function ParallaxComponent() {
|
||||||
|
const { scrollYProgress } = useScroll();
|
||||||
|
const y = useTransform(scrollYProgress, [0, 1], [0, -100]);
|
||||||
|
|
||||||
|
return <motion.div style={{ y }} />;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Exit Animations
|
||||||
|
|
||||||
|
### Use AnimatePresence for Exit Animations
|
||||||
|
```tsx
|
||||||
|
import { AnimatePresence, motion } from "motion/react";
|
||||||
|
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
{isVisible && (
|
||||||
|
<motion.div
|
||||||
|
key="modal"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Staggered List Animation
|
||||||
|
```tsx
|
||||||
|
<motion.ul
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
|
variants={{
|
||||||
|
visible: { transition: { staggerChildren: 0.07 } }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{items.map((item) => (
|
||||||
|
<motion.li
|
||||||
|
key={item.id}
|
||||||
|
variants={{
|
||||||
|
hidden: { opacity: 0, y: 20 },
|
||||||
|
visible: { opacity: 1, y: 0 }
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</motion.ul>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Page Transitions
|
||||||
|
```tsx
|
||||||
|
const pageTransition = {
|
||||||
|
initial: { opacity: 0, x: -20 },
|
||||||
|
animate: { opacity: 1, x: 0 },
|
||||||
|
exit: { opacity: 0, x: 20 },
|
||||||
|
transition: { duration: 0.3 }
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Debugging
|
||||||
|
|
||||||
|
- Use React DevTools to inspect re-renders
|
||||||
|
- Use Chrome DevTools Performance tab to identify animation jank
|
||||||
|
- Target 60fps minimum, 120fps on high refresh rate displays
|
||||||
|
- Test on actual devices, especially mid-range Android phones
|
||||||
240
.agents/skills/shadcn/SKILL.md
Normal file
240
.agents/skills/shadcn/SKILL.md
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
---
|
||||||
|
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".
|
||||||
|
user-invocable: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# shadcn/ui
|
||||||
|
|
||||||
|
A framework for building ui, components and design systems. Components are added as source code to the user's project via the CLI.
|
||||||
|
|
||||||
|
> **IMPORTANT:** Run all CLI commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest` — based on the project's `packageManager`. Examples below use `npx shadcn@latest` but substitute the correct runner for the project.
|
||||||
|
|
||||||
|
## Current Project Context
|
||||||
|
|
||||||
|
```json
|
||||||
|
!`npx shadcn@latest info --json 2>/dev/null || echo '{"error": "No shadcn project found. Run shadcn init first."}'`
|
||||||
|
```
|
||||||
|
|
||||||
|
The JSON above contains the project config and installed components. Use `npx shadcn@latest docs <component>` to get documentation and example URLs for any component.
|
||||||
|
|
||||||
|
## Principles
|
||||||
|
|
||||||
|
1. **Use existing components first.** Use `npx shadcn@latest search` to check registries before writing custom UI. Check community registries too.
|
||||||
|
2. **Compose, don't reinvent.** Settings page = Tabs + Card + form controls. Dashboard = Sidebar + Card + Chart + Table.
|
||||||
|
3. **Use built-in variants before custom styles.** `variant="outline"`, `size="sm"`, etc.
|
||||||
|
4. **Use semantic colors.** `bg-primary`, `text-muted-foreground` — never raw values like `bg-blue-500`.
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
These rules are **always enforced**. Each links to a file with Incorrect/Correct code pairs.
|
||||||
|
|
||||||
|
### Styling & Tailwind → [styling.md](./rules/styling.md)
|
||||||
|
|
||||||
|
- **`className` for layout, not styling.** Never override component colors or typography.
|
||||||
|
- **No `space-x-*` or `space-y-*`.** Use `flex` with `gap-*`. For vertical stacks, `flex flex-col gap-*`.
|
||||||
|
- **Use `size-*` when width and height are equal.** `size-10` not `w-10 h-10`.
|
||||||
|
- **Use `truncate` shorthand.** Not `overflow-hidden text-ellipsis whitespace-nowrap`.
|
||||||
|
- **No manual `dark:` color overrides.** Use semantic tokens (`bg-background`, `text-muted-foreground`).
|
||||||
|
- **Use `cn()` for conditional classes.** Don't write manual template literal ternaries.
|
||||||
|
- **No manual `z-index` on overlay components.** Dialog, Sheet, Popover, etc. handle their own stacking.
|
||||||
|
|
||||||
|
### Forms & Inputs → [forms.md](./rules/forms.md)
|
||||||
|
|
||||||
|
- **Forms use `FieldGroup` + `Field`.** Never use raw `div` with `space-y-*` or `grid gap-*` for form layout.
|
||||||
|
- **`InputGroup` uses `InputGroupInput`/`InputGroupTextarea`.** Never raw `Input`/`Textarea` inside `InputGroup`.
|
||||||
|
- **Buttons inside inputs use `InputGroup` + `InputGroupAddon`.**
|
||||||
|
- **Option sets (2–7 choices) use `ToggleGroup`.** Don't loop `Button` with manual active state.
|
||||||
|
- **`FieldSet` + `FieldLegend` for grouping related checkboxes/radios.** Don't use a `div` with a heading.
|
||||||
|
- **Field validation uses `data-invalid` + `aria-invalid`.** `data-invalid` on `Field`, `aria-invalid` on the control. For disabled: `data-disabled` on `Field`, `disabled` on the control.
|
||||||
|
|
||||||
|
### Component Structure → [composition.md](./rules/composition.md)
|
||||||
|
|
||||||
|
- **Items always inside their Group.** `SelectItem` → `SelectGroup`. `DropdownMenuItem` → `DropdownMenuGroup`. `CommandItem` → `CommandGroup`.
|
||||||
|
- **Use `asChild` (radix) or `render` (base) for custom triggers.** Check `base` field from `npx shadcn@latest info`. → [base-vs-radix.md](./rules/base-vs-radix.md)
|
||||||
|
- **Dialog, Sheet, and Drawer always need a Title.** `DialogTitle`, `SheetTitle`, `DrawerTitle` required for accessibility. Use `className="sr-only"` if visually hidden.
|
||||||
|
- **Use full Card composition.** `CardHeader`/`CardTitle`/`CardDescription`/`CardContent`/`CardFooter`. Don't dump everything in `CardContent`.
|
||||||
|
- **Button has no `isPending`/`isLoading`.** Compose with `Spinner` + `data-icon` + `disabled`.
|
||||||
|
- **`TabsTrigger` must be inside `TabsList`.** Never render triggers directly in `Tabs`.
|
||||||
|
- **`Avatar` always needs `AvatarFallback`.** For when the image fails to load.
|
||||||
|
|
||||||
|
### Use Components, Not Custom Markup → [composition.md](./rules/composition.md)
|
||||||
|
|
||||||
|
- **Use existing components before custom markup.** Check if a component exists before writing a styled `div`.
|
||||||
|
- **Callouts use `Alert`.** Don't build custom styled divs.
|
||||||
|
- **Empty states use `Empty`.** Don't build custom empty state markup.
|
||||||
|
- **Toast via `sonner`.** Use `toast()` from `sonner`.
|
||||||
|
- **Use `Separator`** instead of `<hr>` or `<div className="border-t">`.
|
||||||
|
- **Use `Skeleton`** for loading placeholders. No custom `animate-pulse` divs.
|
||||||
|
- **Use `Badge`** instead of custom styled spans.
|
||||||
|
|
||||||
|
### Icons → [icons.md](./rules/icons.md)
|
||||||
|
|
||||||
|
- **Icons in `Button` use `data-icon`.** `data-icon="inline-start"` or `data-icon="inline-end"` on the icon.
|
||||||
|
- **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.
|
||||||
|
|
||||||
|
### CLI
|
||||||
|
|
||||||
|
- **Never decode or fetch preset codes manually.** Pass them directly to `npx shadcn@latest init --preset <code>`.
|
||||||
|
|
||||||
|
## Key Patterns
|
||||||
|
|
||||||
|
These are the most common patterns that differentiate correct shadcn/ui code. For edge cases, see the linked rule files above.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Form layout: FieldGroup + Field, not div + Label.
|
||||||
|
<FieldGroup>
|
||||||
|
<Field>
|
||||||
|
<FieldLabel htmlFor="email">Email</FieldLabel>
|
||||||
|
<Input id="email" />
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
// Validation: data-invalid on Field, aria-invalid on the control.
|
||||||
|
<Field data-invalid>
|
||||||
|
<FieldLabel>Email</FieldLabel>
|
||||||
|
<Input aria-invalid />
|
||||||
|
<FieldDescription>Invalid email.</FieldDescription>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
// Icons in buttons: data-icon, no sizing classes.
|
||||||
|
<Button>
|
||||||
|
<SearchIcon data-icon="inline-start" />
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
// Spacing: gap-*, not space-y-*.
|
||||||
|
<div className="flex flex-col gap-4"> // correct
|
||||||
|
<div className="space-y-4"> // wrong
|
||||||
|
|
||||||
|
// Equal dimensions: size-*, not w-* h-*.
|
||||||
|
<Avatar className="size-10"> // correct
|
||||||
|
<Avatar className="w-10 h-10"> // wrong
|
||||||
|
|
||||||
|
// Status colors: Badge variants or semantic tokens, not raw colors.
|
||||||
|
<Badge variant="secondary">+20.1%</Badge> // correct
|
||||||
|
<span className="text-emerald-600">+20.1%</span> // wrong
|
||||||
|
```
|
||||||
|
|
||||||
|
## Component Selection
|
||||||
|
|
||||||
|
| Need | Use |
|
||||||
|
| -------------------------- | --------------------------------------------------------------------------------------------------- |
|
||||||
|
| Button/action | `Button` with appropriate variant |
|
||||||
|
| Form inputs | `Input`, `Select`, `Combobox`, `Switch`, `Checkbox`, `RadioGroup`, `Textarea`, `InputOTP`, `Slider` |
|
||||||
|
| Toggle between 2–5 options | `ToggleGroup` + `ToggleGroupItem` |
|
||||||
|
| Data display | `Table`, `Card`, `Badge`, `Avatar` |
|
||||||
|
| Navigation | `Sidebar`, `NavigationMenu`, `Breadcrumb`, `Tabs`, `Pagination` |
|
||||||
|
| Overlays | `Dialog` (modal), `Sheet` (side panel), `Drawer` (bottom sheet), `AlertDialog` (confirmation) |
|
||||||
|
| Feedback | `sonner` (toast), `Alert`, `Progress`, `Skeleton`, `Spinner` |
|
||||||
|
| Command palette | `Command` inside `Dialog` |
|
||||||
|
| Charts | `Chart` (wraps Recharts) |
|
||||||
|
| Layout | `Card`, `Separator`, `Resizable`, `ScrollArea`, `Accordion`, `Collapsible` |
|
||||||
|
| Empty states | `Empty` |
|
||||||
|
| Menus | `DropdownMenu`, `ContextMenu`, `Menubar` |
|
||||||
|
| Tooltips/info | `Tooltip`, `HoverCard`, `Popover` |
|
||||||
|
|
||||||
|
## Key Fields
|
||||||
|
|
||||||
|
The injected project context contains these key fields:
|
||||||
|
|
||||||
|
- **`aliases`** → use the actual alias prefix for imports (e.g. `@/`, `~/`), never hardcode.
|
||||||
|
- **`isRSC`** → when `true`, components using `useState`, `useEffect`, event handlers, or browser APIs need `"use client"` at the top of the file. Always reference this field when advising on the directive.
|
||||||
|
- **`tailwindVersion`** → `"v4"` uses `@theme inline` blocks; `"v3"` uses `tailwind.config.js`.
|
||||||
|
- **`tailwindCssFile`** → the global CSS file where custom CSS variables are defined. Always edit this file, never create a new one.
|
||||||
|
- **`style`** → component visual treatment (e.g. `nova`, `vega`).
|
||||||
|
- **`base`** → primitive library (`radix` or `base`). Affects component APIs and available props.
|
||||||
|
- **`iconLibrary`** → determines icon imports. Use `lucide-react` for `lucide`, `@tabler/icons-react` for `tabler`, etc. Never assume `lucide-react`.
|
||||||
|
- **`resolvedPaths`** → exact file-system destinations for components, utils, hooks, etc.
|
||||||
|
- **`framework`** → routing and file conventions (e.g. Next.js App Router vs Vite SPA).
|
||||||
|
- **`packageManager`** → use this for any non-shadcn dependency installs (e.g. `pnpm add date-fns` vs `npm install date-fns`).
|
||||||
|
|
||||||
|
See [cli.md — `info` command](./cli.md) for the full field reference.
|
||||||
|
|
||||||
|
## Component Docs, Examples, and Usage
|
||||||
|
|
||||||
|
Run `npx shadcn@latest docs <component>` to get the URLs for a component's documentation, examples, and API reference. Fetch these URLs to get the actual content.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest docs button dialog select
|
||||||
|
```
|
||||||
|
|
||||||
|
**When creating, fixing, debugging, or using a component, always run `npx shadcn@latest docs` and fetch the URLs first.** This ensures you're working with the correct API and usage patterns rather than guessing.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. **Get project context** — already injected above. Run `npx shadcn@latest info` again if you need to refresh.
|
||||||
|
2. **Check installed components first** — before running `add`, always check the `components` list from project context or list the `resolvedPaths.ui` directory. Don't import components that haven't been added, and don't re-add ones already installed.
|
||||||
|
3. **Find components** — `npx shadcn@latest search`.
|
||||||
|
4. **Get docs and examples** — run `npx shadcn@latest docs <component>` to get URLs, then fetch them. Use `npx shadcn@latest view` to browse registry items you haven't installed. To preview changes to installed components, use `npx shadcn@latest add --diff`.
|
||||||
|
5. **Install or update** — `npx shadcn@latest add`. When updating existing components, use `--dry-run` and `--diff` to preview changes first (see [Updating Components](#updating-components) below).
|
||||||
|
6. **Fix imports in third-party components** — After adding components from community registries (e.g. `@bundui`, `@magicui`), check the added non-UI files for hardcoded import paths like `@/components/ui/...`. These won't match the project's actual aliases. Use `npx shadcn@latest info` to get the correct `ui` alias (e.g. `@workspace/ui/components`) and rewrite the imports accordingly. The CLI rewrites imports for its own UI files, but third-party registry components may use default paths that don't match the project.
|
||||||
|
7. **Review added components** — After adding a component or block from any registry, **always read the added files and verify they are correct**. Check for missing sub-components (e.g. `SelectItem` without `SelectGroup`), missing imports, incorrect composition, or violations of the [Critical Rules](#critical-rules). Also replace any icon imports with the project's `iconLibrary` from the project context (e.g. if the registry item uses `lucide-react` but the project uses `hugeicons`, swap the imports and icon names accordingly). Fix all issues before moving on.
|
||||||
|
8. **Registry must be explicit** — When the user asks to add a block or component, **do not guess the registry**. If no registry is specified (e.g. user says "add a login block" without specifying `@shadcn`, `@tailark`, etc.), ask which registry to use. Never default to a registry on behalf of the user.
|
||||||
|
9. **Switching presets** — Ask the user first: **reinstall**, **merge**, or **skip**?
|
||||||
|
- **Reinstall**: `npx shadcn@latest init --preset <code> --force --reinstall`. Overwrites all components.
|
||||||
|
- **Merge**: `npx shadcn@latest init --preset <code> --force --no-reinstall`, then run `npx shadcn@latest info` to list installed components, then for each installed component use `--dry-run` and `--diff` to [smart merge](#updating-components) it individually.
|
||||||
|
- **Skip**: `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS, leaves components as-is.
|
||||||
|
|
||||||
|
## Updating Components
|
||||||
|
|
||||||
|
When the user asks to update a component from upstream while keeping their local changes, use `--dry-run` and `--diff` to intelligently merge. **NEVER fetch raw files from GitHub manually — always use the CLI.**
|
||||||
|
|
||||||
|
1. Run `npx shadcn@latest add <component> --dry-run` to see all files that would be affected.
|
||||||
|
2. For each file, run `npx shadcn@latest add <component> --diff <file>` to see what changed upstream vs local.
|
||||||
|
3. Decide per file based on the diff:
|
||||||
|
- No local changes → safe to overwrite.
|
||||||
|
- Has local changes → read the local file, analyze the diff, and apply upstream updates while preserving local modifications.
|
||||||
|
- User says "just update everything" → use `--overwrite`, but confirm first.
|
||||||
|
4. **Never use `--overwrite` without the user's explicit approval.**
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create a new project.
|
||||||
|
npx shadcn@latest init --name my-app --preset base-nova
|
||||||
|
npx shadcn@latest init --name my-app --preset a2r6bw --template vite
|
||||||
|
|
||||||
|
# Create a monorepo project.
|
||||||
|
npx shadcn@latest init --name my-app --preset base-nova --monorepo
|
||||||
|
npx shadcn@latest init --name my-app --preset base-nova --template next --monorepo
|
||||||
|
|
||||||
|
# Initialize existing project.
|
||||||
|
npx shadcn@latest init --preset base-nova
|
||||||
|
npx shadcn@latest init --defaults # shortcut: --template=next --preset=base-nova
|
||||||
|
|
||||||
|
# Add components.
|
||||||
|
npx shadcn@latest add button card dialog
|
||||||
|
npx shadcn@latest add @magicui/shimmer-button
|
||||||
|
npx shadcn@latest add --all
|
||||||
|
|
||||||
|
# Preview changes before adding/updating.
|
||||||
|
npx shadcn@latest add button --dry-run
|
||||||
|
npx shadcn@latest add button --diff button.tsx
|
||||||
|
npx shadcn@latest add @acme/form --view button.tsx
|
||||||
|
|
||||||
|
# Search registries.
|
||||||
|
npx shadcn@latest search @shadcn -q "sidebar"
|
||||||
|
npx shadcn@latest search @tailark -q "stats"
|
||||||
|
|
||||||
|
# Get component docs and example URLs.
|
||||||
|
npx shadcn@latest docs button dialog select
|
||||||
|
|
||||||
|
# View registry item details (for items not yet installed).
|
||||||
|
npx shadcn@latest view @shadcn/button
|
||||||
|
```
|
||||||
|
|
||||||
|
**Named presets:** `base-nova`, `radix-nova`
|
||||||
|
**Templates:** `next`, `vite`, `start`, `react-router`, `astro` (all support `--monorepo`) and `laravel` (not supported for monorepo)
|
||||||
|
**Preset codes:** Base62 strings starting with `a` (e.g. `a2r6bw`), from [ui.shadcn.com](https://ui.shadcn.com).
|
||||||
|
|
||||||
|
## Detailed References
|
||||||
|
|
||||||
|
- [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/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
|
||||||
|
- [cli.md](./cli.md) — Commands, flags, presets, templates
|
||||||
|
- [customization.md](./customization.md) — Theming, CSS variables, extending components
|
||||||
5
.agents/skills/shadcn/agents/openai.yml
Normal file
5
.agents/skills/shadcn/agents/openai.yml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "shadcn/ui"
|
||||||
|
short_description: "Manages shadcn/ui components — adding, searching, fixing, debugging, styling, and composing UI."
|
||||||
|
icon_small: "./assets/shadcn-small.png"
|
||||||
|
icon_large: "./assets/shadcn.png"
|
||||||
BIN
.agents/skills/shadcn/assets/shadcn-small.png
Normal file
BIN
.agents/skills/shadcn/assets/shadcn-small.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
BIN
.agents/skills/shadcn/assets/shadcn.png
Normal file
BIN
.agents/skills/shadcn/assets/shadcn.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
255
.agents/skills/shadcn/cli.md
Normal file
255
.agents/skills/shadcn/cli.md
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
# shadcn CLI Reference
|
||||||
|
|
||||||
|
Configuration is read from `components.json`.
|
||||||
|
|
||||||
|
> **IMPORTANT:** Always run commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest`. Check `packageManager` from project context to choose the right one. Examples below use `npx shadcn@latest` but substitute the correct runner for the project.
|
||||||
|
|
||||||
|
> **IMPORTANT:** Only use the flags documented below. Do not invent or guess flags — if a flag isn't listed here, it doesn't exist. The CLI auto-detects the package manager from the project's lockfile; there is no `--package-manager` flag.
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
- Commands: init, add (dry-run, smart merge), search, view, docs, info, build
|
||||||
|
- Templates: next, vite, start, react-router, astro
|
||||||
|
- Presets: named, code, URL formats and fields
|
||||||
|
- Switching presets
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
### `init` — Initialize or create a project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest init [components...] [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Initializes shadcn/ui in an existing project or creates a new project (when `--name` is provided). Optionally installs components in the same step.
|
||||||
|
|
||||||
|
| Flag | Short | Description | Default |
|
||||||
|
| ----------------------- | ----- | --------------------------------------------------------- | ------- |
|
||||||
|
| `--template <template>` | `-t` | Template (next, start, vite, next-monorepo, react-router) | — |
|
||||||
|
| `--preset [name]` | `-p` | Preset configuration (named, code, or URL) | — |
|
||||||
|
| `--yes` | `-y` | Skip confirmation prompt | `true` |
|
||||||
|
| `--defaults` | `-d` | Use defaults (`--template=next --preset=base-nova`) | `false` |
|
||||||
|
| `--force` | `-f` | Force overwrite existing configuration | `false` |
|
||||||
|
| `--cwd <cwd>` | `-c` | Working directory | current |
|
||||||
|
| `--name <name>` | `-n` | Name for new project | — |
|
||||||
|
| `--silent` | `-s` | Mute output | `false` |
|
||||||
|
| `--rtl` | | Enable RTL support | — |
|
||||||
|
| `--reinstall` | | Re-install existing UI components | `false` |
|
||||||
|
| `--monorepo` | | Scaffold a monorepo project | — |
|
||||||
|
| `--no-monorepo` | | Skip the monorepo prompt | — |
|
||||||
|
|
||||||
|
`npx shadcn@latest create` is an alias for `npx shadcn@latest init`.
|
||||||
|
|
||||||
|
### `add` — Add components
|
||||||
|
|
||||||
|
> **IMPORTANT:** To compare local components against upstream or to preview changes, ALWAYS use `npx shadcn@latest add <component> --dry-run`, `--diff`, or `--view`. NEVER fetch raw files from GitHub or other sources manually. The CLI handles registry resolution, file paths, and CSS diffing automatically.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest add [components...] [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Accepts component names, registry-prefixed names (`@magicui/shimmer-button`), URLs, or local paths.
|
||||||
|
|
||||||
|
| Flag | Short | Description | Default |
|
||||||
|
| --------------- | ----- | -------------------------------------------------------------------------------------------------------------------- | ------- |
|
||||||
|
| `--yes` | `-y` | Skip confirmation prompt | `false` |
|
||||||
|
| `--overwrite` | `-o` | Overwrite existing files | `false` |
|
||||||
|
| `--cwd <cwd>` | `-c` | Working directory | current |
|
||||||
|
| `--all` | `-a` | Add all available components | `false` |
|
||||||
|
| `--path <path>` | `-p` | Target path for the component | — |
|
||||||
|
| `--silent` | `-s` | Mute output | `false` |
|
||||||
|
| `--dry-run` | | Preview all changes without writing files | `false` |
|
||||||
|
| `--diff [path]` | | Show diffs. Without a path, shows the first 5 files. With a path, shows that file only (implies `--dry-run`) | — |
|
||||||
|
| `--view [path]` | | Show file contents. Without a path, shows the first 5 files. With a path, shows that file only (implies `--dry-run`) | — |
|
||||||
|
|
||||||
|
#### Dry-Run Mode
|
||||||
|
|
||||||
|
Use `--dry-run` to preview what `add` would do without writing any files. `--diff` and `--view` both imply `--dry-run`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Preview all changes.
|
||||||
|
npx shadcn@latest add button --dry-run
|
||||||
|
|
||||||
|
# Show diffs for all files (top 5).
|
||||||
|
npx shadcn@latest add button --diff
|
||||||
|
|
||||||
|
# Show the diff for a specific file.
|
||||||
|
npx shadcn@latest add button --diff button.tsx
|
||||||
|
|
||||||
|
# Show contents for all files (top 5).
|
||||||
|
npx shadcn@latest add button --view
|
||||||
|
|
||||||
|
# Show the full content of a specific file.
|
||||||
|
npx shadcn@latest add button --view button.tsx
|
||||||
|
|
||||||
|
# Works with URLs too.
|
||||||
|
npx shadcn@latest add https://api.npoint.io/abc123 --dry-run
|
||||||
|
|
||||||
|
# CSS diffs.
|
||||||
|
npx shadcn@latest add button --diff globals.css
|
||||||
|
```
|
||||||
|
|
||||||
|
**When to use dry-run:**
|
||||||
|
|
||||||
|
- When the user asks "what files will this add?" or "what will this change?" — use `--dry-run`.
|
||||||
|
- Before overwriting existing components — use `--diff` to preview the changes first.
|
||||||
|
- When the user wants to inspect component source code without installing — use `--view`.
|
||||||
|
- When checking what CSS changes would be made to `globals.css` — use `--diff globals.css`.
|
||||||
|
- When the user asks to review or audit third-party registry code before installing — use `--view` to inspect the source.
|
||||||
|
|
||||||
|
> **`npx shadcn@latest add --dry-run` vs `npx shadcn@latest view`:** Prefer `npx shadcn@latest add --dry-run/--diff/--view` over `npx shadcn@latest view` when the user wants to preview changes to their project. `npx shadcn@latest view` only shows raw registry metadata. `npx shadcn@latest add --dry-run` shows exactly what would happen in the user's project: resolved file paths, diffs against existing files, and CSS updates. Use `npx shadcn@latest view` only when the user wants to browse registry info without a project context.
|
||||||
|
|
||||||
|
#### Smart Merge from Upstream
|
||||||
|
|
||||||
|
See [Updating Components in SKILL.md](./SKILL.md#updating-components) for the full workflow.
|
||||||
|
|
||||||
|
### `search` — Search registries
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest search <registries...> [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Fuzzy search across registries. Also aliased as `npx shadcn@latest list`. Without `-q`, lists all items.
|
||||||
|
|
||||||
|
| Flag | Short | Description | Default |
|
||||||
|
| ------------------- | ----- | ---------------------- | ------- |
|
||||||
|
| `--query <query>` | `-q` | Search query | — |
|
||||||
|
| `--limit <number>` | `-l` | Max items per registry | `100` |
|
||||||
|
| `--offset <number>` | `-o` | Items to skip | `0` |
|
||||||
|
| `--cwd <cwd>` | `-c` | Working directory | current |
|
||||||
|
|
||||||
|
### `view` — View item details
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest view <items...> [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Displays item info including file contents. Example: `npx shadcn@latest view @shadcn/button`.
|
||||||
|
|
||||||
|
### `docs` — Get component documentation URLs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest docs <components...> [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Outputs resolved URLs for component documentation, examples, and API references. Accepts one or more component names. Fetch the URLs to get the actual content.
|
||||||
|
|
||||||
|
Example output for `npx shadcn@latest docs input button`:
|
||||||
|
|
||||||
|
```
|
||||||
|
base radix
|
||||||
|
|
||||||
|
input
|
||||||
|
docs https://ui.shadcn.com/docs/components/radix/input
|
||||||
|
examples https://raw.githubusercontent.com/.../examples/input-example.tsx
|
||||||
|
|
||||||
|
button
|
||||||
|
docs https://ui.shadcn.com/docs/components/radix/button
|
||||||
|
examples https://raw.githubusercontent.com/.../examples/button-example.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
Some components include an `api` link to the underlying library (e.g. `cmdk` for the command component).
|
||||||
|
|
||||||
|
### `diff` — Check for updates
|
||||||
|
|
||||||
|
Do not use this command. Use `npx shadcn@latest add --diff` instead.
|
||||||
|
|
||||||
|
### `info` — Project information
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest info [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Displays project info and `components.json` configuration. Run this first to discover the project's framework, aliases, Tailwind version, and resolved paths.
|
||||||
|
|
||||||
|
| Flag | Short | Description | Default |
|
||||||
|
| ------------- | ----- | ----------------- | ------- |
|
||||||
|
| `--cwd <cwd>` | `-c` | Working directory | current |
|
||||||
|
|
||||||
|
**Project Info fields:**
|
||||||
|
|
||||||
|
| Field | Type | Meaning |
|
||||||
|
| -------------------- | --------- | ------------------------------------------------------------------ |
|
||||||
|
| `framework` | `string` | Detected framework (`next`, `vite`, `react-router`, `start`, etc.) |
|
||||||
|
| `frameworkVersion` | `string` | Framework version (e.g. `15.2.4`) |
|
||||||
|
| `isSrcDir` | `boolean` | Whether the project uses a `src/` directory |
|
||||||
|
| `isRSC` | `boolean` | Whether React Server Components are enabled |
|
||||||
|
| `isTsx` | `boolean` | Whether the project uses TypeScript |
|
||||||
|
| `tailwindVersion` | `string` | `"v3"` or `"v4"` |
|
||||||
|
| `tailwindConfigFile` | `string` | Path to the Tailwind config file |
|
||||||
|
| `tailwindCssFile` | `string` | Path to the global CSS file |
|
||||||
|
| `aliasPrefix` | `string` | Import alias prefix (e.g. `@`, `~`, `@/`) |
|
||||||
|
| `packageManager` | `string` | Detected package manager (`npm`, `pnpm`, `yarn`, `bun`) |
|
||||||
|
|
||||||
|
**Components.json fields:**
|
||||||
|
|
||||||
|
| Field | Type | Meaning |
|
||||||
|
| -------------------- | --------- | ------------------------------------------------------------------------------------------ |
|
||||||
|
| `base` | `string` | Primitive library (`radix` or `base`) — determines component APIs and available props |
|
||||||
|
| `style` | `string` | Visual style (e.g. `nova`, `vega`) |
|
||||||
|
| `rsc` | `boolean` | RSC flag from config |
|
||||||
|
| `tsx` | `boolean` | TypeScript flag |
|
||||||
|
| `tailwind.config` | `string` | Tailwind config path |
|
||||||
|
| `tailwind.css` | `string` | Global CSS path — this is where custom CSS variables go |
|
||||||
|
| `iconLibrary` | `string` | Icon library — determines icon import package (e.g. `lucide-react`, `@tabler/icons-react`) |
|
||||||
|
| `aliases.components` | `string` | Component import alias (e.g. `@/components`) |
|
||||||
|
| `aliases.utils` | `string` | Utils import alias (e.g. `@/lib/utils`) |
|
||||||
|
| `aliases.ui` | `string` | UI component alias (e.g. `@/components/ui`) |
|
||||||
|
| `aliases.lib` | `string` | Lib alias (e.g. `@/lib`) |
|
||||||
|
| `aliases.hooks` | `string` | Hooks alias (e.g. `@/hooks`) |
|
||||||
|
| `resolvedPaths` | `object` | Absolute file-system paths for each alias |
|
||||||
|
| `registries` | `object` | Configured custom registries |
|
||||||
|
|
||||||
|
**Links fields:**
|
||||||
|
|
||||||
|
The `info` output includes a **Links** section with templated URLs for component docs, source, and examples. For resolved URLs, use `npx shadcn@latest docs <component>` instead.
|
||||||
|
|
||||||
|
### `build` — Build a custom registry
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest build [registry] [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Builds `registry.json` into individual JSON files for distribution. Default input: `./registry.json`, default output: `./public/r`.
|
||||||
|
|
||||||
|
| Flag | Short | Description | Default |
|
||||||
|
| ----------------- | ----- | ----------------- | ------------ |
|
||||||
|
| `--output <path>` | `-o` | Output directory | `./public/r` |
|
||||||
|
| `--cwd <cwd>` | `-c` | Working directory | current |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Templates
|
||||||
|
|
||||||
|
| Value | Framework | Monorepo support |
|
||||||
|
| -------------- | -------------- | ---------------- |
|
||||||
|
| `next` | Next.js | Yes |
|
||||||
|
| `vite` | Vite | Yes |
|
||||||
|
| `start` | TanStack Start | Yes |
|
||||||
|
| `react-router` | React Router | Yes |
|
||||||
|
| `astro` | Astro | Yes |
|
||||||
|
| `laravel` | Laravel | No |
|
||||||
|
|
||||||
|
All templates support monorepo scaffolding via the `--monorepo` flag. When passed, the CLI uses a monorepo-specific template directory (e.g. `next-monorepo`, `vite-monorepo`). When neither `--monorepo` nor `--no-monorepo` is passed, the CLI prompts interactively. Laravel does not support monorepo scaffolding.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Presets
|
||||||
|
|
||||||
|
Three ways to specify a preset via `--preset`:
|
||||||
|
|
||||||
|
1. **Named:** `--preset base-nova` or `--preset radix-nova`
|
||||||
|
2. **Code:** `--preset a2r6bw` (base62 string, starts with lowercase `a`)
|
||||||
|
3. **URL:** `--preset "https://ui.shadcn.com/init?base=radix&style=nova&..."`
|
||||||
|
|
||||||
|
> **IMPORTANT:** Never try to decode, fetch, or resolve preset codes manually. Preset codes are opaque — pass them directly to `npx shadcn@latest init --preset <code>` and let the CLI handle resolution.
|
||||||
|
|
||||||
|
## Switching Presets
|
||||||
|
|
||||||
|
Ask the user first: **reinstall**, **merge**, or **skip** existing components?
|
||||||
|
|
||||||
|
- **Re-install** → `npx shadcn@latest init --preset <code> --force --reinstall`. Overwrites all component files with the new preset styles. Use when the user hasn't customized components.
|
||||||
|
- **Merge** → `npx shadcn@latest init --preset <code> --force --no-reinstall`, then run `npx shadcn@latest info` to get the list of installed components and use the [smart merge workflow](./SKILL.md#updating-components) to update them one by one, preserving local changes. Use when the user has customized components.
|
||||||
|
- **Skip** → `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS variables, leaves existing components as-is.
|
||||||
202
.agents/skills/shadcn/customization.md
Normal file
202
.agents/skills/shadcn/customization.md
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
# Customization & Theming
|
||||||
|
|
||||||
|
Components reference semantic CSS variable tokens. Change the variables to change every component.
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
- How it works (CSS variables → Tailwind utilities → components)
|
||||||
|
- Color variables and OKLCH format
|
||||||
|
- Dark mode setup
|
||||||
|
- Changing the theme (presets, CSS variables)
|
||||||
|
- Adding custom colors (Tailwind v3 and v4)
|
||||||
|
- Border radius
|
||||||
|
- Customizing components (variants, className, wrappers)
|
||||||
|
- Checking for updates
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. CSS variables defined in `:root` (light) and `.dark` (dark mode).
|
||||||
|
2. Tailwind maps them to utilities: `bg-primary`, `text-muted-foreground`, etc.
|
||||||
|
3. Components use these utilities — changing a variable changes all components that reference it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Color Variables
|
||||||
|
|
||||||
|
Every color follows the `name` / `name-foreground` convention. The base variable is for backgrounds, `-foreground` is for text/icons on that background.
|
||||||
|
|
||||||
|
| Variable | Purpose |
|
||||||
|
| -------------------------------------------- | -------------------------------- |
|
||||||
|
| `--background` / `--foreground` | Page background and default text |
|
||||||
|
| `--card` / `--card-foreground` | Card surfaces |
|
||||||
|
| `--primary` / `--primary-foreground` | Primary buttons and actions |
|
||||||
|
| `--secondary` / `--secondary-foreground` | Secondary actions |
|
||||||
|
| `--muted` / `--muted-foreground` | Muted/disabled states |
|
||||||
|
| `--accent` / `--accent-foreground` | Hover and accent states |
|
||||||
|
| `--destructive` / `--destructive-foreground` | Error and destructive actions |
|
||||||
|
| `--border` | Default border color |
|
||||||
|
| `--input` | Form input borders |
|
||||||
|
| `--ring` | Focus ring color |
|
||||||
|
| `--chart-1` through `--chart-5` | Chart/data visualization |
|
||||||
|
| `--sidebar-*` | Sidebar-specific colors |
|
||||||
|
| `--surface` / `--surface-foreground` | Secondary surface |
|
||||||
|
|
||||||
|
Colors use OKLCH: `--primary: oklch(0.205 0 0)` where values are lightness (0–1), chroma (0 = gray), and hue (0–360).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dark Mode
|
||||||
|
|
||||||
|
Class-based toggle via `.dark` on the root element. In Next.js, use `next-themes`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { ThemeProvider } from "next-themes"
|
||||||
|
|
||||||
|
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
||||||
|
{children}
|
||||||
|
</ThemeProvider>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changing the Theme
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Apply a preset code from ui.shadcn.com.
|
||||||
|
npx shadcn@latest init --preset a2r6bw --force
|
||||||
|
|
||||||
|
# Switch to a named preset.
|
||||||
|
npx shadcn@latest init --preset radix-nova --force
|
||||||
|
npx shadcn@latest init --reinstall # update existing components to match
|
||||||
|
|
||||||
|
# Use a custom theme URL.
|
||||||
|
npx shadcn@latest init --preset "https://ui.shadcn.com/init?base=radix&style=nova&theme=blue&..." --force
|
||||||
|
```
|
||||||
|
|
||||||
|
Or edit CSS variables directly in `globals.css`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Adding Custom Colors
|
||||||
|
|
||||||
|
Add variables to the file at `tailwindCssFile` from `npx shadcn@latest info` (typically `globals.css`). Never create a new CSS file for this.
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* 1. Define in the global CSS file. */
|
||||||
|
:root {
|
||||||
|
--warning: oklch(0.84 0.16 84);
|
||||||
|
--warning-foreground: oklch(0.28 0.07 46);
|
||||||
|
}
|
||||||
|
.dark {
|
||||||
|
--warning: oklch(0.41 0.11 46);
|
||||||
|
--warning-foreground: oklch(0.99 0.02 95);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* 2a. Register with Tailwind v4 (@theme inline). */
|
||||||
|
@theme inline {
|
||||||
|
--color-warning: var(--warning);
|
||||||
|
--color-warning-foreground: var(--warning-foreground);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When `tailwindVersion` is `"v3"` (check via `npx shadcn@latest info`), register in `tailwind.config.js` instead:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 2b. Register with Tailwind v3 (tailwind.config.js).
|
||||||
|
module.exports = {
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
warning: "oklch(var(--warning) / <alpha-value>)",
|
||||||
|
"warning-foreground":
|
||||||
|
"oklch(var(--warning-foreground) / <alpha-value>)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// 3. Use in components.
|
||||||
|
<div className="bg-warning text-warning-foreground">Warning</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Border Radius
|
||||||
|
|
||||||
|
`--radius` controls border radius globally. Components derive values from it (`rounded-lg` = `var(--radius)`, `rounded-md` = `calc(var(--radius) - 2px)`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Customizing Components
|
||||||
|
|
||||||
|
See also: [rules/styling.md](./rules/styling.md) for Incorrect/Correct examples.
|
||||||
|
|
||||||
|
Prefer these approaches in order:
|
||||||
|
|
||||||
|
### 1. Built-in variants
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button variant="outline" size="sm">Click</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Tailwind classes via `className`
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Card className="max-w-md mx-auto">...</Card>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Add a new variant
|
||||||
|
|
||||||
|
Edit the component source to add a variant via `cva`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// components/ui/button.tsx
|
||||||
|
warning: "bg-warning text-warning-foreground hover:bg-warning/90",
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Wrapper components
|
||||||
|
|
||||||
|
Compose shadcn/ui primitives into higher-level components:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
export function ConfirmDialog({ title, description, onConfirm, children }) {
|
||||||
|
return (
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger asChild>{children}</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>{title}</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>{description}</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||||
|
<AlertDialogAction onClick={onConfirm}>Confirm</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Checking for Updates
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest add button --diff
|
||||||
|
```
|
||||||
|
|
||||||
|
To preview exactly what would change before updating, use `--dry-run` and `--diff`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest add button --dry-run # see all affected files
|
||||||
|
npx shadcn@latest add button --diff button.tsx # see the diff for a specific file
|
||||||
|
```
|
||||||
|
|
||||||
|
See [Updating Components in SKILL.md](./SKILL.md#updating-components) for the full smart merge workflow.
|
||||||
47
.agents/skills/shadcn/evals/evals.json
Normal file
47
.agents/skills/shadcn/evals/evals.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"skill_name": "shadcn",
|
||||||
|
"evals": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"prompt": "I'm building a Next.js app with shadcn/ui (base-nova preset, lucide icons). Create a settings form component with fields for: full name, email address, and notification preferences (email, SMS, push notifications as toggle options). Add validation states for required fields.",
|
||||||
|
"expected_output": "A React component using FieldGroup, Field, ToggleGroup, data-invalid/aria-invalid validation, gap-* spacing, and semantic colors.",
|
||||||
|
"files": [],
|
||||||
|
"expectations": [
|
||||||
|
"Uses FieldGroup and Field components for form layout instead of raw div with space-y",
|
||||||
|
"Uses Switch for independent on/off notification toggles (not looping Button with manual active state)",
|
||||||
|
"Uses data-invalid on Field and aria-invalid on the input control for validation states",
|
||||||
|
"Uses gap-* (e.g. gap-4, gap-6) instead of space-y-* or space-x-* for spacing",
|
||||||
|
"Uses semantic color tokens (e.g. bg-background, text-muted-foreground, text-destructive) instead of raw colors like bg-red-500",
|
||||||
|
"No manual dark: color overrides"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"prompt": "Create a dialog component for editing a user profile. It should have the user's avatar at the top, input fields for name and bio, and Save/Cancel buttons with appropriate icons. Using shadcn/ui with radix-nova preset and tabler icons.",
|
||||||
|
"expected_output": "A React component with DialogTitle, Avatar+AvatarFallback, data-icon on icon buttons, no icon sizing classes, tabler icon imports.",
|
||||||
|
"files": [],
|
||||||
|
"expectations": [
|
||||||
|
"Includes DialogTitle for accessibility (visible or with sr-only class)",
|
||||||
|
"Avatar component includes AvatarFallback",
|
||||||
|
"Icons on buttons use the data-icon attribute (data-icon=\"inline-start\" or data-icon=\"inline-end\")",
|
||||||
|
"No sizing classes on icons inside components (no size-4, w-4, h-4, etc.)",
|
||||||
|
"Uses tabler icons (@tabler/icons-react) instead of lucide-react",
|
||||||
|
"Uses asChild for custom triggers (radix preset)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"prompt": "Create a dashboard component that shows 4 stat cards in a grid. Each card has a title, large number, percentage change badge, and a loading skeleton state. Using shadcn/ui with base-nova preset and lucide icons.",
|
||||||
|
"expected_output": "A React component with full Card composition, Skeleton for loading, Badge for changes, semantic colors, gap-* spacing.",
|
||||||
|
"files": [],
|
||||||
|
"expectations": [
|
||||||
|
"Uses full Card composition with CardHeader, CardTitle, CardContent (not dumping everything into CardContent)",
|
||||||
|
"Uses Skeleton component for loading placeholders instead of custom animate-pulse divs",
|
||||||
|
"Uses Badge component for percentage change instead of custom styled spans",
|
||||||
|
"Uses semantic color tokens instead of raw color values like bg-green-500 or text-red-600",
|
||||||
|
"Uses gap-* instead of space-y-* or space-x-* for spacing",
|
||||||
|
"Uses size-* when width and height are equal instead of separate w-* h-*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
94
.agents/skills/shadcn/mcp.md
Normal file
94
.agents/skills/shadcn/mcp.md
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
# shadcn MCP Server
|
||||||
|
|
||||||
|
The CLI includes an MCP server that lets AI assistants search, browse, view, and install components from registries.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
shadcn mcp # start the MCP server (stdio)
|
||||||
|
shadcn mcp init # write config for your editor
|
||||||
|
```
|
||||||
|
|
||||||
|
Editor config files:
|
||||||
|
|
||||||
|
| Editor | Config file |
|
||||||
|
|--------|------------|
|
||||||
|
| Claude Code | `.mcp.json` |
|
||||||
|
| Cursor | `.cursor/mcp.json` |
|
||||||
|
| VS Code | `.vscode/mcp.json` |
|
||||||
|
| OpenCode | `opencode.json` |
|
||||||
|
| Codex | `~/.codex/config.toml` (manual) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
|
||||||
|
> **Tip:** MCP tools handle registry operations (search, view, install). For project configuration (aliases, framework, Tailwind version), use `npx shadcn@latest info` — there is no MCP equivalent.
|
||||||
|
|
||||||
|
### `shadcn:get_project_registries`
|
||||||
|
|
||||||
|
Returns registry names from `components.json`. Errors if no `components.json` exists.
|
||||||
|
|
||||||
|
**Input:** none
|
||||||
|
|
||||||
|
### `shadcn:list_items_in_registries`
|
||||||
|
|
||||||
|
Lists all items from one or more registries.
|
||||||
|
|
||||||
|
**Input:** `registries` (string[]), `limit` (number, optional), `offset` (number, optional)
|
||||||
|
|
||||||
|
### `shadcn:search_items_in_registries`
|
||||||
|
|
||||||
|
Fuzzy search across registries.
|
||||||
|
|
||||||
|
**Input:** `registries` (string[]), `query` (string), `limit` (number, optional), `offset` (number, optional)
|
||||||
|
|
||||||
|
### `shadcn:view_items_in_registries`
|
||||||
|
|
||||||
|
View item details including full file contents.
|
||||||
|
|
||||||
|
**Input:** `items` (string[]) — e.g. `["@shadcn/button", "@shadcn/card"]`
|
||||||
|
|
||||||
|
### `shadcn:get_item_examples_from_registries`
|
||||||
|
|
||||||
|
Find usage examples and demos with source code.
|
||||||
|
|
||||||
|
**Input:** `registries` (string[]), `query` (string) — e.g. `"accordion-demo"`, `"button example"`
|
||||||
|
|
||||||
|
### `shadcn:get_add_command_for_items`
|
||||||
|
|
||||||
|
Returns the CLI install command.
|
||||||
|
|
||||||
|
**Input:** `items` (string[]) — e.g. `["@shadcn/button"]`
|
||||||
|
|
||||||
|
### `shadcn:get_audit_checklist`
|
||||||
|
|
||||||
|
Returns a checklist for verifying components (imports, deps, lint, TypeScript).
|
||||||
|
|
||||||
|
**Input:** none
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuring Registries
|
||||||
|
|
||||||
|
Registries are set in `components.json`. The `@shadcn` registry is always built-in.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"registries": {
|
||||||
|
"@acme": "https://acme.com/r/{name}.json",
|
||||||
|
"@private": {
|
||||||
|
"url": "https://private.com/r/{name}.json",
|
||||||
|
"headers": { "Authorization": "Bearer ${MY_TOKEN}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Names must start with `@`.
|
||||||
|
- URLs must contain `{name}`.
|
||||||
|
- `${VAR}` references are resolved from environment variables.
|
||||||
|
|
||||||
|
Community registry index: `https://ui.shadcn.com/r/registries.json`
|
||||||
306
.agents/skills/shadcn/rules/base-vs-radix.md
Normal file
306
.agents/skills/shadcn/rules/base-vs-radix.md
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
# Base vs Radix
|
||||||
|
|
||||||
|
API differences between `base` and `radix`. Check the `base` field from `npx shadcn@latest info`.
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
- Composition: asChild vs render
|
||||||
|
- Button / trigger as non-button element
|
||||||
|
- Select (items prop, placeholder, positioning, multiple, object values)
|
||||||
|
- ToggleGroup (type vs multiple)
|
||||||
|
- Slider (scalar vs array)
|
||||||
|
- Accordion (type and defaultValue)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Composition: asChild (radix) vs render (base)
|
||||||
|
|
||||||
|
Radix uses `asChild` to replace the default element. Base uses `render`. Don't wrap triggers in extra elements.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<DialogTrigger>
|
||||||
|
<div>
|
||||||
|
<Button>Open</Button>
|
||||||
|
</div>
|
||||||
|
</DialogTrigger>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (radix):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button>Open</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<DialogTrigger render={<Button />}>Open</DialogTrigger>
|
||||||
|
```
|
||||||
|
|
||||||
|
This applies to all trigger and close components: `DialogTrigger`, `SheetTrigger`, `AlertDialogTrigger`, `DropdownMenuTrigger`, `PopoverTrigger`, `TooltipTrigger`, `CollapsibleTrigger`, `DialogClose`, `SheetClose`, `NavigationMenuLink`, `BreadcrumbLink`, `SidebarMenuButton`, `Badge`, `Item`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Button / trigger as non-button element (base only)
|
||||||
|
|
||||||
|
When `render` changes an element to a non-button (`<a>`, `<span>`), add `nativeButton={false}`.
|
||||||
|
|
||||||
|
**Incorrect (base):** missing `nativeButton={false}`.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button render={<a href="/docs" />}>Read the docs</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button render={<a href="/docs" />} nativeButton={false}>
|
||||||
|
Read the docs
|
||||||
|
</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (radix):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button asChild>
|
||||||
|
<a href="/docs">Read the docs</a>
|
||||||
|
</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
Same for triggers whose `render` is not a `Button`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// base.
|
||||||
|
<PopoverTrigger render={<InputGroupAddon />} nativeButton={false}>
|
||||||
|
Pick date
|
||||||
|
</PopoverTrigger>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Select
|
||||||
|
|
||||||
|
**items prop (base only).** Base requires an `items` prop on the root. Radix uses inline JSX only.
|
||||||
|
|
||||||
|
**Incorrect (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger><SelectValue placeholder="Select a fruit" /></SelectTrigger>
|
||||||
|
</Select>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const items = [
|
||||||
|
{ label: "Select a fruit", value: null },
|
||||||
|
{ label: "Apple", value: "apple" },
|
||||||
|
{ label: "Banana", value: "banana" },
|
||||||
|
]
|
||||||
|
|
||||||
|
<Select items={items}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
{items.map((item) => (
|
||||||
|
<SelectItem key={item.value} value={item.value}>{item.label}</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (radix):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a fruit" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectItem value="apple">Apple</SelectItem>
|
||||||
|
<SelectItem value="banana">Banana</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Placeholder.** Base uses a `{ value: null }` item in the items array. Radix uses `<SelectValue placeholder="...">`.
|
||||||
|
|
||||||
|
**Content positioning.** Base uses `alignItemWithTrigger`. Radix uses `position`.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// base.
|
||||||
|
<SelectContent alignItemWithTrigger={false} side="bottom">
|
||||||
|
|
||||||
|
// radix.
|
||||||
|
<SelectContent position="popper">
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Select — multiple selection and object values (base only)
|
||||||
|
|
||||||
|
Base supports `multiple`, render-function children on `SelectValue`, and object values with `itemToStringValue`. Radix is single-select with string values only.
|
||||||
|
|
||||||
|
**Correct (base — multiple selection):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Select items={items} multiple defaultValue={[]}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue>
|
||||||
|
{(value: string[]) => value.length === 0 ? "Select fruits" : `${value.length} selected`}
|
||||||
|
</SelectValue>
|
||||||
|
</SelectTrigger>
|
||||||
|
...
|
||||||
|
</Select>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (base — object values):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Select defaultValue={plans[0]} itemToStringValue={(plan) => plan.name}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue>{(value) => value.name}</SelectValue>
|
||||||
|
</SelectTrigger>
|
||||||
|
...
|
||||||
|
</Select>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ToggleGroup
|
||||||
|
|
||||||
|
Base uses a `multiple` boolean prop. Radix uses `type="single"` or `type="multiple"`.
|
||||||
|
|
||||||
|
**Incorrect (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<ToggleGroup type="single" defaultValue="daily">
|
||||||
|
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Single (no prop needed), defaultValue is always an array.
|
||||||
|
<ToggleGroup defaultValue={["daily"]} spacing={2}>
|
||||||
|
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
|
||||||
|
// Multi-selection.
|
||||||
|
<ToggleGroup multiple>
|
||||||
|
<ToggleGroupItem value="bold">Bold</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="italic">Italic</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (radix):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Single, defaultValue is a string.
|
||||||
|
<ToggleGroup type="single" defaultValue="daily" spacing={2}>
|
||||||
|
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
|
||||||
|
// Multi-selection.
|
||||||
|
<ToggleGroup type="multiple">
|
||||||
|
<ToggleGroupItem value="bold">Bold</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="italic">Italic</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Controlled single value:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// base — wrap/unwrap arrays.
|
||||||
|
const [value, setValue] = React.useState("normal")
|
||||||
|
<ToggleGroup value={[value]} onValueChange={(v) => setValue(v[0])}>
|
||||||
|
|
||||||
|
// radix — plain string.
|
||||||
|
const [value, setValue] = React.useState("normal")
|
||||||
|
<ToggleGroup type="single" value={value} onValueChange={setValue}>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Slider
|
||||||
|
|
||||||
|
Base accepts a plain number for a single thumb. Radix always requires an array.
|
||||||
|
|
||||||
|
**Incorrect (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Slider defaultValue={[50]} max={100} step={1} />
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Slider defaultValue={50} max={100} step={1} />
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (radix):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Slider defaultValue={[50]} max={100} step={1} />
|
||||||
|
```
|
||||||
|
|
||||||
|
Both use arrays for range sliders. Controlled `onValueChange` in base may need a cast:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// base.
|
||||||
|
const [value, setValue] = React.useState([0.3, 0.7])
|
||||||
|
<Slider value={value} onValueChange={(v) => setValue(v as number[])} />
|
||||||
|
|
||||||
|
// radix.
|
||||||
|
const [value, setValue] = React.useState([0.3, 0.7])
|
||||||
|
<Slider value={value} onValueChange={setValue} />
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Accordion
|
||||||
|
|
||||||
|
Radix requires `type="single"` or `type="multiple"` and supports `collapsible`. `defaultValue` is a string. Base uses no `type` prop, uses `multiple` boolean, and `defaultValue` is always an array.
|
||||||
|
|
||||||
|
**Incorrect (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Accordion type="single" collapsible defaultValue="item-1">
|
||||||
|
<AccordionItem value="item-1">...</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Accordion defaultValue={["item-1"]}>
|
||||||
|
<AccordionItem value="item-1">...</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
// Multi-select.
|
||||||
|
<Accordion multiple defaultValue={["item-1", "item-2"]}>
|
||||||
|
<AccordionItem value="item-1">...</AccordionItem>
|
||||||
|
<AccordionItem value="item-2">...</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (radix):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Accordion type="single" collapsible defaultValue="item-1">
|
||||||
|
<AccordionItem value="item-1">...</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
```
|
||||||
195
.agents/skills/shadcn/rules/composition.md
Normal file
195
.agents/skills/shadcn/rules/composition.md
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
# 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:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="apple">Apple</SelectItem>
|
||||||
|
<SelectItem value="banana">Banana</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<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
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Alert>
|
||||||
|
<AlertTitle>Warning</AlertTitle>
|
||||||
|
<AlertDescription>Something needs attention.</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Empty states use Empty component
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<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
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
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.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Edit Profile</DialogTitle>
|
||||||
|
<DialogDescription>Update your profile.</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
...
|
||||||
|
</DialogContent>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Card structure
|
||||||
|
|
||||||
|
Use full composition — don't dump everything into `CardContent`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<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`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button disabled>
|
||||||
|
<Spinner data-icon="inline-start" />
|
||||||
|
Saving...
|
||||||
|
</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TabsTrigger must be inside TabsList
|
||||||
|
|
||||||
|
Never render `TabsTrigger` directly inside `Tabs` — always wrap in `TabsList`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<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:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<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">` |
|
||||||
192
.agents/skills/shadcn/rules/forms.md
Normal file
192
.agents/skills/shadcn/rules/forms.md
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
# Forms & Inputs
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
- Forms use FieldGroup + Field
|
||||||
|
- InputGroup requires InputGroupInput/InputGroupTextarea
|
||||||
|
- Buttons inside inputs use InputGroup + InputGroupAddon
|
||||||
|
- Option sets (2–7 choices) use ToggleGroup
|
||||||
|
- FieldSet + FieldLegend for grouping related fields
|
||||||
|
- Field validation and disabled states
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Forms use FieldGroup + Field
|
||||||
|
|
||||||
|
Always use `FieldGroup` + `Field` — never raw `div` with `space-y-*`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<FieldGroup>
|
||||||
|
<Field>
|
||||||
|
<FieldLabel htmlFor="email">Email</FieldLabel>
|
||||||
|
<Input id="email" type="email" />
|
||||||
|
</Field>
|
||||||
|
<Field>
|
||||||
|
<FieldLabel htmlFor="password">Password</FieldLabel>
|
||||||
|
<Input id="password" type="password" />
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `Field orientation="horizontal"` for settings pages. Use `FieldLabel className="sr-only"` for visually hidden labels.
|
||||||
|
|
||||||
|
**Choosing form controls:**
|
||||||
|
|
||||||
|
- Simple text input → `Input`
|
||||||
|
- Dropdown with predefined options → `Select`
|
||||||
|
- Searchable dropdown → `Combobox`
|
||||||
|
- Native HTML select (no JS) → `native-select`
|
||||||
|
- Boolean toggle → `Switch` (for settings) or `Checkbox` (for forms)
|
||||||
|
- Single choice from few options → `RadioGroup`
|
||||||
|
- Toggle between 2–5 options → `ToggleGroup` + `ToggleGroupItem`
|
||||||
|
- OTP/verification code → `InputOTP`
|
||||||
|
- Multi-line text → `Textarea`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## InputGroup requires InputGroupInput/InputGroupTextarea
|
||||||
|
|
||||||
|
Never use raw `Input` or `Textarea` inside an `InputGroup`.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<InputGroup>
|
||||||
|
<Input placeholder="Search..." />
|
||||||
|
</InputGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { InputGroup, InputGroupInput } from "@/components/ui/input-group"
|
||||||
|
|
||||||
|
<InputGroup>
|
||||||
|
<InputGroupInput placeholder="Search..." />
|
||||||
|
</InputGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Buttons inside inputs use InputGroup + InputGroupAddon
|
||||||
|
|
||||||
|
Never place a `Button` directly inside or adjacent to an `Input` with custom positioning.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<div className="relative">
|
||||||
|
<Input placeholder="Search..." className="pr-10" />
|
||||||
|
<Button className="absolute right-0 top-0" size="icon">
|
||||||
|
<SearchIcon />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { InputGroup, InputGroupInput, InputGroupAddon } from "@/components/ui/input-group"
|
||||||
|
|
||||||
|
<InputGroup>
|
||||||
|
<InputGroupInput placeholder="Search..." />
|
||||||
|
<InputGroupAddon>
|
||||||
|
<Button size="icon">
|
||||||
|
<SearchIcon data-icon="inline-start" />
|
||||||
|
</Button>
|
||||||
|
</InputGroupAddon>
|
||||||
|
</InputGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Option sets (2–7 choices) use ToggleGroup
|
||||||
|
|
||||||
|
Don't manually loop `Button` components with active state.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const [selected, setSelected] = useState("daily")
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{["daily", "weekly", "monthly"].map((option) => (
|
||||||
|
<Button
|
||||||
|
key={option}
|
||||||
|
variant={selected === option ? "default" : "outline"}
|
||||||
|
onClick={() => setSelected(option)}
|
||||||
|
>
|
||||||
|
{option}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
|
||||||
|
|
||||||
|
<ToggleGroup spacing={2}>
|
||||||
|
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="monthly">Monthly</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
Combine with `Field` for labelled toggle groups:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Field orientation="horizontal">
|
||||||
|
<FieldTitle id="theme-label">Theme</FieldTitle>
|
||||||
|
<ToggleGroup aria-labelledby="theme-label" spacing={2}>
|
||||||
|
<ToggleGroupItem value="light">Light</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="dark">Dark</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="system">System</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
</Field>
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** `defaultValue` and `type`/`multiple` props differ between base and radix. See [base-vs-radix.md](./base-vs-radix.md#togglegroup).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## FieldSet + FieldLegend for grouping related fields
|
||||||
|
|
||||||
|
Use `FieldSet` + `FieldLegend` for related checkboxes, radios, or switches — not `div` with a heading:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<FieldSet>
|
||||||
|
<FieldLegend variant="label">Preferences</FieldLegend>
|
||||||
|
<FieldDescription>Select all that apply.</FieldDescription>
|
||||||
|
<FieldGroup className="gap-3">
|
||||||
|
<Field orientation="horizontal">
|
||||||
|
<Checkbox id="dark" />
|
||||||
|
<FieldLabel htmlFor="dark" className="font-normal">Dark mode</FieldLabel>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
</FieldSet>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Field validation and disabled states
|
||||||
|
|
||||||
|
Both attributes are needed — `data-invalid`/`data-disabled` styles the field (label, description), while `aria-invalid`/`disabled` styles the control.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Invalid.
|
||||||
|
<Field data-invalid>
|
||||||
|
<FieldLabel htmlFor="email">Email</FieldLabel>
|
||||||
|
<Input id="email" aria-invalid />
|
||||||
|
<FieldDescription>Invalid email address.</FieldDescription>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
// Disabled.
|
||||||
|
<Field data-disabled>
|
||||||
|
<FieldLabel htmlFor="email">Email</FieldLabel>
|
||||||
|
<Input id="email" disabled />
|
||||||
|
</Field>
|
||||||
|
```
|
||||||
|
|
||||||
|
Works for all controls: `Input`, `Textarea`, `Select`, `Checkbox`, `RadioGroupItem`, `Switch`, `Slider`, `NativeSelect`, `InputOTP`.
|
||||||
101
.agents/skills/shadcn/rules/icons.md
Normal file
101
.agents/skills/shadcn/rules/icons.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Icons
|
||||||
|
|
||||||
|
**Always use the project's configured `iconLibrary` for imports.** Check the `iconLibrary` field from project context: `lucide` → `lucide-react`, `tabler` → `@tabler/icons-react`, etc. Never assume `lucide-react`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Icons in Button use data-icon attribute
|
||||||
|
|
||||||
|
Add `data-icon="inline-start"` (prefix) or `data-icon="inline-end"` (suffix) to the icon. No sizing classes on the icon.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button>
|
||||||
|
<SearchIcon className="mr-2 size-4" />
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button>
|
||||||
|
<SearchIcon data-icon="inline-start"/>
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button>
|
||||||
|
Next
|
||||||
|
<ArrowRightIcon data-icon="inline-end"/>
|
||||||
|
</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## No sizing classes on icons inside components
|
||||||
|
|
||||||
|
Components handle icon sizing via CSS. Don't add `size-4`, `w-4 h-4`, or other sizing classes to icons inside `Button`, `DropdownMenuItem`, `Alert`, `Sidebar*`, or other shadcn components. Unless the user explicitly asks for custom icon sizes.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button>
|
||||||
|
<SearchIcon className="size-4" data-icon="inline-start" />
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<SettingsIcon className="mr-2 size-4" />
|
||||||
|
Settings
|
||||||
|
</DropdownMenuItem>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button>
|
||||||
|
<SearchIcon data-icon="inline-start" />
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<SettingsIcon />
|
||||||
|
Settings
|
||||||
|
</DropdownMenuItem>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pass icons as component objects, not string keys
|
||||||
|
|
||||||
|
Use `icon={CheckIcon}`, not a string key to a lookup map.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const iconMap = {
|
||||||
|
check: CheckIcon,
|
||||||
|
alert: AlertIcon,
|
||||||
|
}
|
||||||
|
|
||||||
|
function StatusBadge({ icon }: { icon: string }) {
|
||||||
|
const Icon = iconMap[icon]
|
||||||
|
return <Icon />
|
||||||
|
}
|
||||||
|
|
||||||
|
<StatusBadge icon="check" />
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Import from the project's configured iconLibrary (e.g. lucide-react, @tabler/icons-react).
|
||||||
|
import { CheckIcon } from "lucide-react"
|
||||||
|
|
||||||
|
function StatusBadge({ icon: Icon }: { icon: React.ComponentType }) {
|
||||||
|
return <Icon />
|
||||||
|
}
|
||||||
|
|
||||||
|
<StatusBadge icon={CheckIcon} />
|
||||||
|
```
|
||||||
162
.agents/skills/shadcn/rules/styling.md
Normal file
162
.agents/skills/shadcn/rules/styling.md
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
# Styling & Customization
|
||||||
|
|
||||||
|
See [customization.md](../customization.md) for theming, CSS variables, and adding custom colors.
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
- Semantic colors
|
||||||
|
- Built-in variants first
|
||||||
|
- className for layout only
|
||||||
|
- No space-x-* / space-y-*
|
||||||
|
- Prefer size-* over w-* h-* when equal
|
||||||
|
- Prefer truncate shorthand
|
||||||
|
- No manual dark: color overrides
|
||||||
|
- Use cn() for conditional classes
|
||||||
|
- No manual z-index on overlay components
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Semantic colors
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<div className="bg-blue-500 text-white">
|
||||||
|
<p className="text-gray-600">Secondary text</p>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<div className="bg-primary text-primary-foreground">
|
||||||
|
<p className="text-muted-foreground">Secondary text</p>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## No raw color values for status/state indicators
|
||||||
|
|
||||||
|
For positive, negative, or status indicators, use Badge variants, semantic tokens like `text-destructive`, or define custom CSS variables — don't reach for raw Tailwind colors.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<span className="text-emerald-600">+20.1%</span>
|
||||||
|
<span className="text-green-500">Active</span>
|
||||||
|
<span className="text-red-600">-3.2%</span>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Badge variant="secondary">+20.1%</Badge>
|
||||||
|
<Badge>Active</Badge>
|
||||||
|
<span className="text-destructive">-3.2%</span>
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need a success/positive color that doesn't exist as a semantic token, use a Badge variant or ask the user about adding a custom CSS variable to the theme (see [customization.md](../customization.md)).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Built-in variants first
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button className="border border-input bg-transparent hover:bg-accent">
|
||||||
|
Click me
|
||||||
|
</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button variant="outline">Click me</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## className for layout only
|
||||||
|
|
||||||
|
Use `className` for layout (e.g. `max-w-md`, `mx-auto`, `mt-4`), **not** for overriding component colors or typography. To change colors, use semantic tokens, built-in variants, or CSS variables.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Card className="bg-blue-100 text-blue-900 font-bold">
|
||||||
|
<CardContent>Dashboard</CardContent>
|
||||||
|
</Card>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Card className="max-w-md mx-auto">
|
||||||
|
<CardContent>Dashboard</CardContent>
|
||||||
|
</Card>
|
||||||
|
```
|
||||||
|
|
||||||
|
To customize a component's appearance, prefer these approaches in order:
|
||||||
|
1. **Built-in variants** — `variant="outline"`, `variant="destructive"`, etc.
|
||||||
|
2. **Semantic color tokens** — `bg-primary`, `text-muted-foreground`.
|
||||||
|
3. **CSS variables** — define custom colors in the global CSS file (see [customization.md](../customization.md)).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## No space-x-* / space-y-*
|
||||||
|
|
||||||
|
Use `gap-*` instead. `space-y-4` → `flex flex-col gap-4`. `space-x-2` → `flex gap-2`.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<Input />
|
||||||
|
<Input />
|
||||||
|
<Button>Submit</Button>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prefer size-* over w-* h-* when equal
|
||||||
|
|
||||||
|
`size-10` not `w-10 h-10`. Applies to icons, avatars, skeletons, etc.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prefer truncate shorthand
|
||||||
|
|
||||||
|
`truncate` not `overflow-hidden text-ellipsis whitespace-nowrap`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## No manual dark: color overrides
|
||||||
|
|
||||||
|
Use semantic tokens — they handle light/dark via CSS variables. `bg-background text-foreground` not `bg-white dark:bg-gray-950`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Use cn() for conditional classes
|
||||||
|
|
||||||
|
Use the `cn()` utility from the project for conditional or merged class names. Don't write manual ternaries in className strings.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<div className={`flex items-center ${isActive ? "bg-primary text-primary-foreground" : "bg-muted"}`}>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
<div className={cn("flex items-center", isActive ? "bg-primary text-primary-foreground" : "bg-muted")}>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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]`.
|
||||||
594
.agents/skills/tailwindcss-advanced-layouts/SKILL.md
Normal file
594
.agents/skills/tailwindcss-advanced-layouts/SKILL.md
Normal file
@@ -0,0 +1,594 @@
|
|||||||
|
---
|
||||||
|
name: tailwindcss-advanced-layouts
|
||||||
|
description: Tailwind CSS advanced layout techniques including CSS Grid and Flexbox patterns
|
||||||
|
---
|
||||||
|
|
||||||
|
# Tailwind CSS Advanced Layout Techniques
|
||||||
|
|
||||||
|
## CSS Grid Mastery
|
||||||
|
|
||||||
|
### Complex Grid Layouts
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Holy Grail Layout -->
|
||||||
|
<div class="grid min-h-screen grid-rows-[auto_1fr_auto]">
|
||||||
|
<header class="bg-white shadow">Header</header>
|
||||||
|
<div class="grid grid-cols-[250px_1fr_300px]">
|
||||||
|
<aside class="bg-gray-50 p-4">Sidebar</aside>
|
||||||
|
<main class="p-6">Main Content</main>
|
||||||
|
<aside class="bg-gray-50 p-4">Right Sidebar</aside>
|
||||||
|
</div>
|
||||||
|
<footer class="bg-gray-800 text-white">Footer</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Responsive Holy Grail -->
|
||||||
|
<div class="grid min-h-screen grid-rows-[auto_1fr_auto]">
|
||||||
|
<header>Header</header>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-[250px_1fr] lg:grid-cols-[250px_1fr_300px]">
|
||||||
|
<aside class="order-2 md:order-1">Sidebar</aside>
|
||||||
|
<main class="order-1 md:order-2">Main</main>
|
||||||
|
<aside class="order-3 hidden lg:block">Right</aside>
|
||||||
|
</div>
|
||||||
|
<footer>Footer</footer>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Grid Template Areas
|
||||||
|
|
||||||
|
```css
|
||||||
|
@utility grid-areas-dashboard {
|
||||||
|
grid-template-areas:
|
||||||
|
"header header header"
|
||||||
|
"nav main aside"
|
||||||
|
"nav footer footer";
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility area-header { grid-area: header; }
|
||||||
|
@utility area-nav { grid-area: nav; }
|
||||||
|
@utility area-main { grid-area: main; }
|
||||||
|
@utility area-aside { grid-area: aside; }
|
||||||
|
@utility area-footer { grid-area: footer; }
|
||||||
|
```
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="grid grid-areas-dashboard grid-cols-[200px_1fr_250px] grid-rows-[60px_1fr_40px] min-h-screen">
|
||||||
|
<header class="area-header bg-white shadow">Header</header>
|
||||||
|
<nav class="area-nav bg-gray-100">Navigation</nav>
|
||||||
|
<main class="area-main p-6">Main Content</main>
|
||||||
|
<aside class="area-aside bg-gray-50 p-4">Sidebar</aside>
|
||||||
|
<footer class="area-footer bg-gray-800 text-white">Footer</footer>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Auto-Fill and Auto-Fit Grids
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Auto-fill: Creates as many tracks as fit, even empty ones -->
|
||||||
|
<div class="grid grid-cols-[repeat(auto-fill,minmax(250px,1fr))] gap-6">
|
||||||
|
<div class="bg-white rounded-lg shadow p-4">Card 1</div>
|
||||||
|
<div class="bg-white rounded-lg shadow p-4">Card 2</div>
|
||||||
|
<div class="bg-white rounded-lg shadow p-4">Card 3</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Auto-fit: Collapses empty tracks -->
|
||||||
|
<div class="grid grid-cols-[repeat(auto-fit,minmax(250px,1fr))] gap-6">
|
||||||
|
<!-- Cards stretch to fill available space -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- With arbitrary values -->
|
||||||
|
<div class="grid grid-cols-[repeat(auto-fill,minmax(min(100%,300px),1fr))] gap-4">
|
||||||
|
<!-- Handles edge case where container is smaller than minmax min -->
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Subgrid
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Enable subgrid in v4 */
|
||||||
|
@utility subgrid-cols {
|
||||||
|
grid-template-columns: subgrid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility subgrid-rows {
|
||||||
|
grid-template-rows: subgrid;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="grid grid-cols-4 gap-4">
|
||||||
|
<!-- Span 2 columns but align children to parent grid -->
|
||||||
|
<div class="col-span-2 grid subgrid-cols gap-4">
|
||||||
|
<div>Aligned to parent column 1</div>
|
||||||
|
<div>Aligned to parent column 2</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced Flexbox Patterns
|
||||||
|
|
||||||
|
### Space Distribution
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Equal spacing with first/last at edges -->
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<div>First</div>
|
||||||
|
<div>Second</div>
|
||||||
|
<div>Third</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Equal spacing everywhere including edges -->
|
||||||
|
<div class="flex justify-around">
|
||||||
|
<div>Item</div>
|
||||||
|
<div>Item</div>
|
||||||
|
<div>Item</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Double space between items vs edges -->
|
||||||
|
<div class="flex justify-evenly">
|
||||||
|
<div>Item</div>
|
||||||
|
<div>Item</div>
|
||||||
|
<div>Item</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flexible Item Sizing
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Items share space equally -->
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-1">1/3</div>
|
||||||
|
<div class="flex-1">1/3</div>
|
||||||
|
<div class="flex-1">1/3</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- First item takes 2x space -->
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-[2]">2/4</div>
|
||||||
|
<div class="flex-1">1/4</div>
|
||||||
|
<div class="flex-1">1/4</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Fixed + flexible -->
|
||||||
|
<div class="flex">
|
||||||
|
<div class="w-64 shrink-0">Fixed 256px</div>
|
||||||
|
<div class="flex-1 min-w-0">Flexible (can shrink)</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Prevent shrinking with text overflow -->
|
||||||
|
<div class="flex min-w-0">
|
||||||
|
<div class="shrink-0">Icon</div>
|
||||||
|
<div class="min-w-0 truncate">Very long text that should truncate</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Masonry-Like with Flexbox
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Column-based masonry -->
|
||||||
|
<div class="flex flex-col flex-wrap h-[800px] gap-4">
|
||||||
|
<div class="w-[calc(33.333%-1rem)] h-48">Item 1</div>
|
||||||
|
<div class="w-[calc(33.333%-1rem)] h-64">Item 2</div>
|
||||||
|
<div class="w-[calc(33.333%-1rem)] h-32">Item 3</div>
|
||||||
|
<!-- Items flow vertically then wrap to next column -->
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Container Queries
|
||||||
|
|
||||||
|
### Basic Container Queries
|
||||||
|
|
||||||
|
```css
|
||||||
|
@plugin "@tailwindcss/container-queries";
|
||||||
|
```
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Define container -->
|
||||||
|
<div class="@container">
|
||||||
|
<!-- Respond to container width -->
|
||||||
|
<div class="flex flex-col @md:flex-row @lg:grid @lg:grid-cols-3 gap-4">
|
||||||
|
<div>Item 1</div>
|
||||||
|
<div>Item 2</div>
|
||||||
|
<div>Item 3</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Named Containers
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Multiple named containers -->
|
||||||
|
<div class="@container/sidebar">
|
||||||
|
<nav class="@[200px]/sidebar:flex-col @[300px]/sidebar:flex-row">
|
||||||
|
Navigation
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="@container/main">
|
||||||
|
<article class="@[600px]/main:prose-lg @[900px]/main:prose-xl">
|
||||||
|
Content
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Container Query Units
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Size relative to container -->
|
||||||
|
<div class="@container">
|
||||||
|
<h1 class="text-[5cqw]">Scales with container width</h1>
|
||||||
|
<p class="text-[3cqi]">Scales with container inline size</p>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Position and Layering
|
||||||
|
|
||||||
|
### Sticky Positioning
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Sticky header -->
|
||||||
|
<header class="sticky top-0 z-50 bg-white/80 backdrop-blur-sm border-b">
|
||||||
|
Navigation
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Sticky sidebar -->
|
||||||
|
<aside class="sticky top-20 h-[calc(100vh-5rem)] overflow-auto">
|
||||||
|
Sidebar content
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- Sticky table header -->
|
||||||
|
<div class="overflow-auto max-h-96">
|
||||||
|
<table>
|
||||||
|
<thead class="sticky top-0 bg-white shadow">
|
||||||
|
<tr>
|
||||||
|
<th class="sticky left-0 bg-white z-10">Corner cell</th>
|
||||||
|
<th>Column 2</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>...</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fixed Elements
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Fixed bottom navigation (mobile) -->
|
||||||
|
<nav class="fixed bottom-0 inset-x-0 z-50 bg-white border-t md:hidden">
|
||||||
|
<div class="flex justify-around py-2">
|
||||||
|
<a href="#">Home</a>
|
||||||
|
<a href="#">Search</a>
|
||||||
|
<a href="#">Profile</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Fixed action button -->
|
||||||
|
<button class="fixed bottom-6 right-6 z-40 rounded-full bg-brand-500 p-4 shadow-lg">
|
||||||
|
<PlusIcon />
|
||||||
|
</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Z-Index Management
|
||||||
|
|
||||||
|
```css
|
||||||
|
@theme {
|
||||||
|
--z-dropdown: 100;
|
||||||
|
--z-sticky: 200;
|
||||||
|
--z-fixed: 300;
|
||||||
|
--z-modal-backdrop: 400;
|
||||||
|
--z-modal: 500;
|
||||||
|
--z-popover: 600;
|
||||||
|
--z-tooltip: 700;
|
||||||
|
--z-toast: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility z-dropdown { z-index: var(--z-dropdown); }
|
||||||
|
@utility z-sticky { z-index: var(--z-sticky); }
|
||||||
|
@utility z-fixed { z-index: var(--z-fixed); }
|
||||||
|
@utility z-modal-backdrop { z-index: var(--z-modal-backdrop); }
|
||||||
|
@utility z-modal { z-index: var(--z-modal); }
|
||||||
|
@utility z-popover { z-index: var(--z-popover); }
|
||||||
|
@utility z-tooltip { z-index: var(--z-tooltip); }
|
||||||
|
@utility z-toast { z-index: var(--z-toast); }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Overflow and Scrolling
|
||||||
|
|
||||||
|
### Custom Scrollbars
|
||||||
|
|
||||||
|
```css
|
||||||
|
@utility scrollbar-thin {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility scrollbar-none {
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility scrollbar-none::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom scrollbar styling */
|
||||||
|
@utility scrollbar-custom {
|
||||||
|
scrollbar-color: oklch(0.7 0 0) oklch(0.95 0 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility scrollbar-custom::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility scrollbar-custom::-webkit-scrollbar-track {
|
||||||
|
background: oklch(0.95 0 0);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility scrollbar-custom::-webkit-scrollbar-thumb {
|
||||||
|
background: oklch(0.7 0 0);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility scrollbar-custom::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: oklch(0.5 0 0);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scroll Snap
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Horizontal carousel -->
|
||||||
|
<div class="flex snap-x snap-mandatory overflow-x-auto gap-4 pb-4">
|
||||||
|
<div class="snap-start shrink-0 w-80">Card 1</div>
|
||||||
|
<div class="snap-start shrink-0 w-80">Card 2</div>
|
||||||
|
<div class="snap-start shrink-0 w-80">Card 3</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Full-page sections -->
|
||||||
|
<div class="h-screen snap-y snap-mandatory overflow-y-auto">
|
||||||
|
<section class="h-screen snap-start">Section 1</section>
|
||||||
|
<section class="h-screen snap-start">Section 2</section>
|
||||||
|
<section class="h-screen snap-start">Section 3</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Snap with padding -->
|
||||||
|
<div class="snap-x scroll-pl-6 overflow-x-auto">
|
||||||
|
<div class="snap-start">...</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scroll Margin for Anchors
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Offset for fixed header -->
|
||||||
|
<section id="about" class="scroll-mt-20">
|
||||||
|
<!-- Content appears below fixed header when linked -->
|
||||||
|
</section>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Aspect Ratio and Object Fit
|
||||||
|
|
||||||
|
### Responsive Aspect Ratios
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Fixed aspect ratio container -->
|
||||||
|
<div class="aspect-video bg-gray-100">
|
||||||
|
<video class="h-full w-full object-cover">...</video>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="aspect-square rounded-full overflow-hidden">
|
||||||
|
<img src="avatar.jpg" class="h-full w-full object-cover" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Custom aspect ratio -->
|
||||||
|
<div class="aspect-[4/3]">4:3 content</div>
|
||||||
|
<div class="aspect-[21/9]">Ultra-wide content</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Object Positioning
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Focus on specific part of image -->
|
||||||
|
<div class="h-64 overflow-hidden">
|
||||||
|
<img
|
||||||
|
src="portrait.jpg"
|
||||||
|
class="h-full w-full object-cover object-top"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Arbitrary object position -->
|
||||||
|
<img class="object-cover object-[25%_75%]" src="..." />
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced Spacing
|
||||||
|
|
||||||
|
### Logical Properties
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Works for LTR and RTL -->
|
||||||
|
<div class="ps-4 pe-6 ms-auto">
|
||||||
|
Padding and margin that respect text direction
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Block direction (vertical in horizontal writing modes) -->
|
||||||
|
<div class="pbs-4 pbe-6 mbs-auto">
|
||||||
|
Block-direction spacing
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Space Between with Dividers
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Dividers between items -->
|
||||||
|
<ul class="divide-y divide-gray-200">
|
||||||
|
<li class="py-4">Item 1</li>
|
||||||
|
<li class="py-4">Item 2</li>
|
||||||
|
<li class="py-4">Item 3</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!-- Horizontal dividers -->
|
||||||
|
<div class="flex divide-x divide-gray-200">
|
||||||
|
<div class="px-4">Section 1</div>
|
||||||
|
<div class="px-4">Section 2</div>
|
||||||
|
<div class="px-4">Section 3</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Negative Margins for Bleeds
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Full-bleed image in padded container -->
|
||||||
|
<article class="px-6">
|
||||||
|
<p>Padded content</p>
|
||||||
|
<img src="hero.jpg" class="-mx-6 w-[calc(100%+3rem)]" />
|
||||||
|
<p>More padded content</p>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<!-- Pull quote that breaks out -->
|
||||||
|
<div class="max-w-prose mx-auto px-4">
|
||||||
|
<p>Normal content...</p>
|
||||||
|
<blockquote class="-mx-8 md:-mx-16 px-8 md:px-16 py-8 bg-gray-100">
|
||||||
|
Featured quote that extends beyond content width
|
||||||
|
</blockquote>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Multi-Column Layout
|
||||||
|
|
||||||
|
### Text Columns
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Responsive columns -->
|
||||||
|
<div class="columns-1 sm:columns-2 lg:columns-3 gap-8">
|
||||||
|
<p>Content flows across columns...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Fixed-width columns -->
|
||||||
|
<div class="columns-[300px] gap-6">
|
||||||
|
<p>Creates as many 300px columns as fit</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Prevent breaks inside elements -->
|
||||||
|
<div class="columns-2">
|
||||||
|
<div class="break-inside-avoid mb-4">
|
||||||
|
Card that stays together
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Responsive Patterns
|
||||||
|
|
||||||
|
### Container Queries + Media Queries
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="@container">
|
||||||
|
<div class="
|
||||||
|
/* Container query for component-level responsiveness */
|
||||||
|
@md:flex @md:gap-4
|
||||||
|
|
||||||
|
/* Media query for page-level responsiveness */
|
||||||
|
lg:grid lg:grid-cols-2
|
||||||
|
">
|
||||||
|
Content
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Breakpoint-Based Visibility
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Show different content per breakpoint -->
|
||||||
|
<nav>
|
||||||
|
<!-- Mobile menu button -->
|
||||||
|
<button class="md:hidden">Menu</button>
|
||||||
|
|
||||||
|
<!-- Desktop navigation -->
|
||||||
|
<ul class="hidden md:flex gap-4">
|
||||||
|
<li>Home</li>
|
||||||
|
<li>About</li>
|
||||||
|
<li>Contact</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fluid Sizing with Clamp
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Fluid padding -->
|
||||||
|
<section class="py-[clamp(2rem,5vw,6rem)] px-[clamp(1rem,3vw,4rem)]">
|
||||||
|
Content with responsive padding
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Fluid max-width -->
|
||||||
|
<div class="mx-auto w-full max-w-[clamp(300px,90vw,1200px)]">
|
||||||
|
Responsive container
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Print Styles
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Hide elements when printing -->
|
||||||
|
<nav class="print:hidden">Navigation</nav>
|
||||||
|
|
||||||
|
<!-- Show only when printing -->
|
||||||
|
<div class="hidden print:block">Print-only content</div>
|
||||||
|
|
||||||
|
<!-- Print-specific styles -->
|
||||||
|
<article class="print:text-black print:bg-white">
|
||||||
|
<h1 class="text-2xl print:text-xl">Heading</h1>
|
||||||
|
<a href="..." class="text-blue-500 print:text-black print:underline">
|
||||||
|
Link (shows as text when printed)
|
||||||
|
</a>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<!-- Prevent page breaks -->
|
||||||
|
<div class="print:break-inside-avoid">
|
||||||
|
Keep this content together on one page
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Force page break -->
|
||||||
|
<div class="print:break-before-page">
|
||||||
|
Start on new page
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### 1. Use Modern Layout Methods
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Prefer Grid for 2D layouts -->
|
||||||
|
<div class="grid grid-cols-3 gap-4">
|
||||||
|
|
||||||
|
<!-- Prefer Flexbox for 1D layouts -->
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Handle Edge Cases
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Prevent flex item from overflowing -->
|
||||||
|
<div class="flex min-w-0">
|
||||||
|
<div class="min-w-0 truncate">Long text</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Prevent grid blowout -->
|
||||||
|
<div class="grid grid-cols-1 min-w-0">
|
||||||
|
<div class="overflow-hidden">Content that might overflow</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Use Semantic Sizing
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Prefer max-w-prose for reading content -->
|
||||||
|
<article class="max-w-prose mx-auto">
|
||||||
|
|
||||||
|
<!-- Use container for page sections -->
|
||||||
|
<div class="container mx-auto px-4">
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Test All Breakpoints
|
||||||
|
|
||||||
|
Create systematic tests for all responsive layouts to ensure they work at every breakpoint.
|
||||||
5
.agents/skills/tailwindcss/GENERATION.md
Normal file
5
.agents/skills/tailwindcss/GENERATION.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Generation Info
|
||||||
|
|
||||||
|
- **Source:** `sources/tailwindcss`
|
||||||
|
- **Git SHA:** `148fcb26ab87892177ec9a7eba4b31e2ee960039`
|
||||||
|
- **Generated:** 2026-01-28
|
||||||
150
.agents/skills/tailwindcss/SKILL.md
Normal file
150
.agents/skills/tailwindcss/SKILL.md
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
---
|
||||||
|
name: tailwindcss
|
||||||
|
description: Tailwind CSS utility-first CSS framework. Use when styling web applications with utility classes, building responsive designs, or customizing design systems with theme variables.
|
||||||
|
metadata:
|
||||||
|
author: Hairyf
|
||||||
|
version: "2026.2.2"
|
||||||
|
source: Generated from https://github.com/tailwindlabs/tailwindcss.com, scripts located at https://github.com/hairyf/skills
|
||||||
|
---
|
||||||
|
|
||||||
|
# Tailwind CSS
|
||||||
|
|
||||||
|
> The skill is based on Tailwind CSS v4.1.18, generated at 2026-01-28.
|
||||||
|
|
||||||
|
Tailwind CSS is a utility-first CSS framework for rapidly building custom user interfaces. Instead of writing custom CSS, you compose designs using utility classes directly in your markup. Tailwind v4 introduces CSS-first configuration with theme variables, making it easier to customize your design system.
|
||||||
|
|
||||||
|
## Core References
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Installation | Vite, PostCSS, CLI, and CDN setup | [core-installation](references/core-installation.md) |
|
||||||
|
| Utility Classes | Understanding Tailwind's utility-first approach and styling elements | [core-utility-classes](references/core-utility-classes.md) |
|
||||||
|
| Theme Variables | Design tokens, customizing theme, and theme variable namespaces | [core-theme](references/core-theme.md) |
|
||||||
|
| Responsive Design | Mobile-first breakpoints, responsive variants, and container queries | [core-responsive](references/core-responsive.md) |
|
||||||
|
| Variants | Applying utilities conditionally with state, pseudo-class, and media query variants | [core-variants](references/core-variants.md) |
|
||||||
|
| Preflight | Tailwind's base styles and how to extend or disable them | [core-preflight](references/core-preflight.md) |
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
### Display & Flexbox & Grid
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Display | flex, grid, block, inline, hidden, sr-only, flow-root, contents | [layout-display](references/layout-display.md) |
|
||||||
|
| Flexbox | flex-direction, justify, items, gap, grow, shrink, wrap, order | [layout-flexbox](references/layout-flexbox.md) |
|
||||||
|
| Grid | grid-cols, grid-rows, gap, place-items, col-span, row-span, subgrid | [layout-grid](references/layout-grid.md) |
|
||||||
|
| Aspect Ratio | Controlling element aspect ratio for responsive media | [layout-aspect-ratio](references/layout-aspect-ratio.md) |
|
||||||
|
| Columns | Multi-column layout for magazine-style or masonry layouts | [layout-columns](references/layout-columns.md) |
|
||||||
|
|
||||||
|
### Positioning
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Position | Controlling element positioning with static, relative, absolute, fixed, and sticky | [layout-position](references/layout-position.md) |
|
||||||
|
| Inset | Controlling placement of positioned elements with top, right, bottom, left, and inset utilities | [layout-inset](references/layout-inset.md) |
|
||||||
|
|
||||||
|
### Sizing
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Width | Setting element width with spacing scale, fractions, container sizes, and viewport units | [layout-width](references/layout-width.md) |
|
||||||
|
| Height | Setting element height with spacing scale, fractions, viewport units, and content-based sizing | [layout-height](references/layout-height.md) |
|
||||||
|
| Min & Max Sizing | min-width, max-width, min-height, max-height constraints | [layout-min-max-sizing](references/layout-min-max-sizing.md) |
|
||||||
|
|
||||||
|
### Spacing
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Margin | Controlling element margins with spacing scale, negative values, logical properties, and space utilities | [layout-margin](references/layout-margin.md) |
|
||||||
|
| Padding | Controlling element padding with spacing scale, logical properties, and directional utilities | [layout-padding](references/layout-padding.md) |
|
||||||
|
|
||||||
|
### Overflow
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Overflow | Controlling how elements handle content that overflows their container | [layout-overflow](references/layout-overflow.md) |
|
||||||
|
|
||||||
|
### Images & Replaced Elements
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Object Fit & Position | Controlling how images and video are resized and positioned | [layout-object-fit-position](references/layout-object-fit-position.md) |
|
||||||
|
|
||||||
|
### Tables
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Table Layout | border-collapse, table-auto, table-fixed | [layout-tables](references/layout-tables.md) |
|
||||||
|
|
||||||
|
## Transforms
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Transform Base | Base transform utilities for enabling transforms, hardware acceleration, and custom transform values | [transform-base](references/transform-base.md) |
|
||||||
|
| Translate | Translating elements on x, y, and z axes with spacing scale, percentages, and custom values | [transform-translate](references/transform-translate.md) |
|
||||||
|
| Rotate | Rotating elements in 2D and 3D space with degree values and custom rotations | [transform-rotate](references/transform-rotate.md) |
|
||||||
|
| Scale | Scaling elements uniformly or on specific axes with percentage values | [transform-scale](references/transform-scale.md) |
|
||||||
|
| Skew | Skewing elements on x and y axes with degree values | [transform-skew](references/transform-skew.md) |
|
||||||
|
|
||||||
|
## Typography
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Font & Text | Font size, weight, color, line-height, letter-spacing, decoration, truncate | [typography-font-text](references/typography-font-text.md) |
|
||||||
|
| Text Align | Controlling text alignment with left, center, right, justify, and logical properties | [typography-text-align](references/typography-text-align.md) |
|
||||||
|
| List Style | list-style-type, list-style-position for bullets and markers | [typography-list-style](references/typography-list-style.md) |
|
||||||
|
|
||||||
|
## Visual
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Background | Background color, gradient, image, size, position | [visual-background](references/visual-background.md) |
|
||||||
|
| Border | Border width, color, radius, divide, ring | [visual-border](references/visual-border.md) |
|
||||||
|
| Effects | Box shadow, opacity, mix-blend, backdrop-blur, filter | [visual-effects](references/visual-effects.md) |
|
||||||
|
| SVG | fill, stroke, stroke-width for SVG and icon styling | [visual-svg](references/visual-svg.md) |
|
||||||
|
|
||||||
|
## Effects & Interactivity
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Transition & Animation | CSS transitions, animation keyframes, reduced motion | [effects-transition-animation](references/effects-transition-animation.md) |
|
||||||
|
| Visibility & Interactivity | Visibility, cursor, pointer-events, user-select, z-index | [effects-visibility-interactivity](references/effects-visibility-interactivity.md) |
|
||||||
|
| Form Controls | accent-color, appearance, caret-color, resize | [effects-form-controls](references/effects-form-controls.md) |
|
||||||
|
| Scroll Snap | scroll-snap-type, scroll-snap-align for carousels | [effects-scroll-snap](references/effects-scroll-snap.md) |
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### Dark Mode
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Dark Mode | Implementing dark mode with the dark variant and custom strategies | [features-dark-mode](references/features-dark-mode.md) |
|
||||||
|
|
||||||
|
### Migration
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Upgrade Guide | Migrating from v3 to v4, breaking changes, rename mappings | [features-upgrade](references/features-upgrade.md) |
|
||||||
|
|
||||||
|
### Customization
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Custom Styles | Adding custom styles, utilities, variants, and working with arbitrary values | [features-custom-styles](references/features-custom-styles.md) |
|
||||||
|
| Functions & Directives | Tailwind's CSS directives and functions for working with your design system | [features-functions-directives](references/features-functions-directives.md) |
|
||||||
|
| Content Detection | How Tailwind detects classes and how to customize content scanning | [features-content-detection](references/features-content-detection.md) |
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Utility Patterns | Managing duplication, conflicts, important modifier, when to use components | [best-practices-utility-patterns](references/best-practices-utility-patterns.md) |
|
||||||
|
|
||||||
|
## Key Recommendations
|
||||||
|
|
||||||
|
- **Use utility classes directly in markup** - Compose designs by combining utilities
|
||||||
|
- **Customize with theme variables** - Use `@theme` directive to define design tokens
|
||||||
|
- **Mobile-first responsive design** - Use unprefixed utilities for mobile, prefixed for breakpoints
|
||||||
|
- **Use complete class names** - Never construct classes dynamically with string interpolation
|
||||||
|
- **Leverage variants** - Stack variants for complex conditional styling
|
||||||
|
- **Prefer CSS-first configuration** - Use `@theme`, `@utility`, and `@custom-variant` over JavaScript configs
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
---
|
||||||
|
name: best-practices-utility-patterns
|
||||||
|
description: Managing duplication, conflicts, important modifier, and when to use components
|
||||||
|
---
|
||||||
|
|
||||||
|
# Best Practices: Utility Patterns
|
||||||
|
|
||||||
|
Practical patterns when building with Tailwind utilities.
|
||||||
|
|
||||||
|
## Managing duplication
|
||||||
|
|
||||||
|
**Use components** for repeated UI: Extract into React/Vue/Svelte components or template partials.
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
function Button({ children, variant = 'primary' }) {
|
||||||
|
const base = 'px-4 py-2 rounded-lg font-medium'
|
||||||
|
const variants = {
|
||||||
|
primary: 'bg-blue-500 hover:bg-blue-600 text-white',
|
||||||
|
secondary: 'bg-gray-200 hover:bg-gray-300 text-gray-900',
|
||||||
|
}
|
||||||
|
return <button className={`${base} ${variants[variant]}`}>{children}</button>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use loops** when markup is repeated from data—the class list is written once.
|
||||||
|
|
||||||
|
**Multi-cursor editing** for localized duplication in a single file.
|
||||||
|
|
||||||
|
## Managing conflicts
|
||||||
|
|
||||||
|
When two utilities target the same property, the one **later in the stylesheet** wins. Avoid adding conflicting classes:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Bad -->
|
||||||
|
<div class="flex grid">Conflict</div>
|
||||||
|
|
||||||
|
<!-- Good: conditional class -->
|
||||||
|
<div class={gridLayout ? 'grid' : 'flex'}>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Important modifier
|
||||||
|
|
||||||
|
Add `!` suffix to force `!important`:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="bg-teal-500 bg-red-500!">Red wins</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Use sparingly; prefer fixing specificity properly.
|
||||||
|
|
||||||
|
## Important flag (global)
|
||||||
|
|
||||||
|
```css
|
||||||
|
@import "tailwindcss" important;
|
||||||
|
```
|
||||||
|
|
||||||
|
Makes all utilities `!important`. Useful when integrating into existing high-specificity CSS.
|
||||||
|
|
||||||
|
## Prefix option
|
||||||
|
|
||||||
|
```css
|
||||||
|
@import "tailwindcss" prefix(tw);
|
||||||
|
```
|
||||||
|
|
||||||
|
Generates `tw:text-red-500` etc. Use when project class names conflict with Tailwind.
|
||||||
|
|
||||||
|
## When to use inline styles
|
||||||
|
|
||||||
|
- Values from API/database (e.g. brand colors)
|
||||||
|
- Dynamic values that change at runtime
|
||||||
|
- Complex arbitrary values hard to read as class names
|
||||||
|
- Pattern: set CSS variables via inline style, reference with `bg-(--my-var)`
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Extract repeated patterns into components
|
||||||
|
- Never add two conflicting utilities—use conditional classes
|
||||||
|
- `!` suffix = single utility `!important`
|
||||||
|
- `important` flag = all utilities `!important`
|
||||||
|
- `prefix(tw)` = prefix all utilities
|
||||||
|
- Use inline styles for dynamic values; utilities for static design
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/utility-first#managing-duplication
|
||||||
|
- https://tailwindcss.com/docs/utility-first#managing-style-conflicts
|
||||||
|
-->
|
||||||
89
.agents/skills/tailwindcss/references/core-installation.md
Normal file
89
.agents/skills/tailwindcss/references/core-installation.md
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
---
|
||||||
|
name: core-installation
|
||||||
|
description: Installing Tailwind CSS with Vite, PostCSS, CLI, or CDN
|
||||||
|
---
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
How to add Tailwind CSS to a project.
|
||||||
|
|
||||||
|
## Vite (Recommended)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install tailwindcss @tailwindcss/vite
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// vite.config.ts
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [tailwindcss()],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* style.css */
|
||||||
|
@import "tailwindcss";
|
||||||
|
```
|
||||||
|
|
||||||
|
## PostCSS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install tailwindcss @tailwindcss/postcss postcss
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
// postcss.config.js
|
||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
'@tailwindcss/postcss': {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
@import "tailwindcss";
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tailwind CLI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx @tailwindcss/cli -i ./src/input.css -o ./dist/output.css --watch
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* input.css */
|
||||||
|
@import "tailwindcss";
|
||||||
|
```
|
||||||
|
|
||||||
|
## Play CDN (Development only)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
Not for production: no purging, larger payload.
|
||||||
|
|
||||||
|
## Framework guides
|
||||||
|
|
||||||
|
- **Nuxt**: `@nuxtjs/tailwindcss` module or `@tailwindcss/vite`
|
||||||
|
- **Next.js**: Use Vite or PostCSS with `tailwind.config.js` if needed
|
||||||
|
- **React Router / SvelteKit / SolidJS**: Use `@tailwindcss/vite`
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Vite: `@tailwindcss/vite` plugin + `@import "tailwindcss"`
|
||||||
|
- PostCSS: `@tailwindcss/postcss`
|
||||||
|
- CLI: `npx @tailwindcss/cli`
|
||||||
|
- v4 uses CSS-first config; no `tailwind.config.js` required for basics
|
||||||
|
- Use `@theme` in CSS to customize design tokens
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/installation/using-vite
|
||||||
|
- https://tailwindcss.com/docs/installation/using-postcss
|
||||||
|
- https://tailwindcss.com/docs/installation/tailwind-cli
|
||||||
|
- https://tailwindcss.com/docs/installation/play-cdn
|
||||||
|
-->
|
||||||
200
.agents/skills/tailwindcss/references/core-preflight.md
Normal file
200
.agents/skills/tailwindcss/references/core-preflight.md
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
---
|
||||||
|
name: core-preflight
|
||||||
|
description: Understanding Tailwind's Preflight base styles and how to extend or disable them
|
||||||
|
---
|
||||||
|
|
||||||
|
# Preflight
|
||||||
|
|
||||||
|
Preflight is Tailwind's opinionated set of base styles that smooth over cross-browser inconsistencies.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Built on top of modern-normalize, Preflight is automatically injected when you import `tailwindcss`:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@layer theme, base, components, utilities;
|
||||||
|
|
||||||
|
@import "tailwindcss/theme.css" layer(theme);
|
||||||
|
@import "tailwindcss/preflight.css" layer(base);
|
||||||
|
@import "tailwindcss/utilities.css" layer(utilities);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Resets
|
||||||
|
|
||||||
|
### Margins Removed
|
||||||
|
|
||||||
|
All default margins are removed from headings, paragraphs, blockquotes, etc:
|
||||||
|
|
||||||
|
```css
|
||||||
|
*,
|
||||||
|
::after,
|
||||||
|
::before {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This prevents accidentally relying on browser default margins that aren't part of your spacing scale.
|
||||||
|
|
||||||
|
### Border Styles Reset
|
||||||
|
|
||||||
|
Borders are reset to make adding borders easier:
|
||||||
|
|
||||||
|
```css
|
||||||
|
*,
|
||||||
|
::after,
|
||||||
|
::before {
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 0 solid;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Since the `border` utility only sets `border-width`, this ensures adding `border` always creates a solid `1px` border using `currentColor`.
|
||||||
|
|
||||||
|
### Headings Unstyled
|
||||||
|
|
||||||
|
All headings are unstyled by default:
|
||||||
|
|
||||||
|
```css
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Reasons:**
|
||||||
|
- Avoids deviating from your type scale
|
||||||
|
- In UI development, headings should often be visually de-emphasized
|
||||||
|
|
||||||
|
### Lists Unstyled
|
||||||
|
|
||||||
|
Ordered and unordered lists have no bullets or numbers:
|
||||||
|
|
||||||
|
```css
|
||||||
|
ol, ul, menu {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Style lists using utilities:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<ul class="list-inside list-disc">
|
||||||
|
<li>One</li>
|
||||||
|
<li>Two</li>
|
||||||
|
</ul>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Accessibility:** Add `role="list"` for screen readers when keeping lists unstyled:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<ul role="list">
|
||||||
|
<li>One</li>
|
||||||
|
<li>Two</li>
|
||||||
|
</ul>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Images Are Block-Level
|
||||||
|
|
||||||
|
Images and replaced elements are `display: block`:
|
||||||
|
|
||||||
|
```css
|
||||||
|
img, svg, video, canvas, audio, iframe, embed, object {
|
||||||
|
display: block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `inline` utility if needed:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<img class="inline" src="..." alt="..." />
|
||||||
|
```
|
||||||
|
|
||||||
|
### Images Are Constrained
|
||||||
|
|
||||||
|
Images and videos are constrained to parent width:
|
||||||
|
|
||||||
|
```css
|
||||||
|
img, video {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Override with `max-w-none`:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<img class="max-w-none" src="..." alt="..." />
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hidden Attribute
|
||||||
|
|
||||||
|
Elements with `hidden` attribute stay hidden:
|
||||||
|
|
||||||
|
```css
|
||||||
|
[hidden]:where(:not([hidden="until-found"])) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Extending Preflight
|
||||||
|
|
||||||
|
Add base styles to the `base` layer:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@layer base {
|
||||||
|
h1 {
|
||||||
|
font-size: var(--text-2xl);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: var(--text-xl);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-blue-600);
|
||||||
|
text-decoration-line: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Disabling Preflight
|
||||||
|
|
||||||
|
Import Tailwind components individually, omitting Preflight:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@layer theme, base, components, utilities;
|
||||||
|
|
||||||
|
@import "tailwindcss/theme.css" layer(theme);
|
||||||
|
/* @import "tailwindcss/preflight.css" layer(base); */ /* Omitted */
|
||||||
|
@import "tailwindcss/utilities.css" layer(utilities);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Working Around Third-Party Libraries
|
||||||
|
|
||||||
|
Some libraries may conflict with Preflight. Override Preflight styles:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@layer base {
|
||||||
|
.google-map * {
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Preflight normalizes cross-browser inconsistencies
|
||||||
|
- Margins, borders, headings, and lists are reset
|
||||||
|
- Images are block-level and constrained by default
|
||||||
|
- Extend Preflight with `@layer base`
|
||||||
|
- Disable by omitting the preflight import
|
||||||
|
- Override Preflight styles when needed for third-party libraries
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/preflight
|
||||||
|
-->
|
||||||
163
.agents/skills/tailwindcss/references/core-responsive.md
Normal file
163
.agents/skills/tailwindcss/references/core-responsive.md
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
---
|
||||||
|
name: core-responsive
|
||||||
|
description: Building responsive designs with Tailwind's mobile-first breakpoint system and container queries
|
||||||
|
---
|
||||||
|
|
||||||
|
# Responsive Design
|
||||||
|
|
||||||
|
Every utility class in Tailwind can be applied conditionally at different breakpoints using responsive variants.
|
||||||
|
|
||||||
|
## Mobile-First Breakpoints
|
||||||
|
|
||||||
|
Tailwind uses a mobile-first approach. Unprefixed utilities apply to all screen sizes, while prefixed utilities apply at that breakpoint and above.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Center text on mobile, left align on sm screens and up -->
|
||||||
|
<div class="text-center sm:text-left">Content</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Default Breakpoints
|
||||||
|
|
||||||
|
| Breakpoint | Minimum Width | CSS |
|
||||||
|
|------------|---------------|-----|
|
||||||
|
| `sm` | 40rem (640px) | `@media (width >= 40rem)` |
|
||||||
|
| `md` | 48rem (768px) | `@media (width >= 48rem)` |
|
||||||
|
| `lg` | 64rem (1024px) | `@media (width >= 64rem)` |
|
||||||
|
| `xl` | 80rem (1280px) | `@media (width >= 80rem)` |
|
||||||
|
| `2xl` | 96rem (1536px) | `@media (width >= 96rem)` |
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Prefix any utility with a breakpoint name:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Width of 16 by default, 32 on medium screens, 48 on large screens -->
|
||||||
|
<img class="w-16 md:w-32 lg:w-48" src="..." />
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example: Responsive Card
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="mx-auto max-w-md md:max-w-2xl">
|
||||||
|
<div class="md:flex">
|
||||||
|
<div class="md:shrink-0">
|
||||||
|
<img class="h-48 w-full object-cover md:h-full md:w-48" src="..." />
|
||||||
|
</div>
|
||||||
|
<div class="p-8">
|
||||||
|
<h2 class="text-lg font-semibold">Title</h2>
|
||||||
|
<p class="text-gray-500">Description</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Targeting Mobile Screens
|
||||||
|
|
||||||
|
Use unprefixed utilities for mobile, not `sm:`. Think of `sm:` as "at the small breakpoint", not "on small screens".
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- ❌ Only centers on screens 640px+, not mobile -->
|
||||||
|
<div class="sm:text-center"></div>
|
||||||
|
|
||||||
|
<!-- ✅ Centers on mobile, left aligns on 640px+ -->
|
||||||
|
<div class="text-center sm:text-left"></div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Breakpoint Ranges
|
||||||
|
|
||||||
|
Target a specific breakpoint range by stacking responsive variants with `max-*` variants:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Only applies between md and xl -->
|
||||||
|
<div class="md:max-xl:flex">Content</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Max-Width Variants
|
||||||
|
|
||||||
|
Tailwind generates `max-*` variants for each breakpoint:
|
||||||
|
|
||||||
|
| Variant | Media Query |
|
||||||
|
|---------|-------------|
|
||||||
|
| `max-sm` | `@media (width < 40rem)` |
|
||||||
|
| `max-md` | `@media (width < 48rem)` |
|
||||||
|
| `max-lg` | `@media (width < 64rem)` |
|
||||||
|
| `max-xl` | `@media (width < 80rem)` |
|
||||||
|
| `max-2xl` | `@media (width < 96rem)` |
|
||||||
|
|
||||||
|
## Custom Breakpoints
|
||||||
|
|
||||||
|
Add custom breakpoints using `--breakpoint-*` theme variables:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@theme {
|
||||||
|
--breakpoint-xs: 30rem;
|
||||||
|
--breakpoint-3xl: 120rem;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="xs:grid-cols-2 3xl:grid-cols-6">Content</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Arbitrary Breakpoints
|
||||||
|
|
||||||
|
Use arbitrary values for one-off breakpoints:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="min-[320px]:text-center max-[600px]:bg-sky-300">
|
||||||
|
Content
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Container Queries
|
||||||
|
|
||||||
|
Style elements based on parent container size instead of viewport:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Mark container -->
|
||||||
|
<div class="@container">
|
||||||
|
<!-- Style based on container size -->
|
||||||
|
<div class="flex flex-col @md:flex-row">
|
||||||
|
Content
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Container Query Variants
|
||||||
|
|
||||||
|
| Variant | Minimum Width |
|
||||||
|
|---------|---------------|
|
||||||
|
| `@3xs` | 16rem (256px) |
|
||||||
|
| `@xs` | 20rem (320px) |
|
||||||
|
| `@sm` | 24rem (384px) |
|
||||||
|
| `@md` | 28rem (448px) |
|
||||||
|
| `@lg` | 32rem (512px) |
|
||||||
|
| `@xl` | 36rem (576px) |
|
||||||
|
| `@2xl` | 42rem (672px) |
|
||||||
|
| `@3xl` | 48rem (768px) |
|
||||||
|
| ... up to `@7xl` | 80rem (1280px) |
|
||||||
|
|
||||||
|
### Named Containers
|
||||||
|
|
||||||
|
Name containers to target specific ones in nested structures:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="@container/main">
|
||||||
|
<div class="flex flex-row @sm/main:flex-col">
|
||||||
|
Content
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Mobile-first: unprefixed = mobile, prefixed = breakpoint and up
|
||||||
|
- Use unprefixed utilities for mobile, not `sm:`
|
||||||
|
- Stack variants for complex responsive behavior
|
||||||
|
- Container queries enable component-based responsive design
|
||||||
|
- Customize breakpoints with theme variables
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/responsive-design
|
||||||
|
-->
|
||||||
108
.agents/skills/tailwindcss/references/core-theme.md
Normal file
108
.agents/skills/tailwindcss/references/core-theme.md
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
---
|
||||||
|
name: core-theme
|
||||||
|
description: Understanding theme variables, design tokens, and customizing Tailwind's default theme
|
||||||
|
---
|
||||||
|
|
||||||
|
# Theme Variables
|
||||||
|
|
||||||
|
Theme variables are special CSS variables defined using the `@theme` directive that influence which utility classes exist in your project.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Theme variables store design tokens like colors, fonts, spacing, shadows, and breakpoints. They're defined using the `@theme` directive:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--color-mint-500: oklch(0.72 0.11 178);
|
||||||
|
--font-display: "Satoshi", "sans-serif";
|
||||||
|
--breakpoint-3xl: 120rem;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now utilities like `bg-mint-500`, `font-display`, and `3xl:grid-cols-6` become available.
|
||||||
|
|
||||||
|
## Why @theme Instead of :root?
|
||||||
|
|
||||||
|
Theme variables aren't just CSS variables - they also instruct Tailwind to create new utility classes. Using `@theme` makes this explicit and ensures variables are defined top-level.
|
||||||
|
|
||||||
|
Use `@theme` for design tokens that map to utilities. Use `:root` for regular CSS variables that shouldn't have corresponding utilities.
|
||||||
|
|
||||||
|
## Theme Variable Namespaces
|
||||||
|
|
||||||
|
Theme variables are organized into namespaces that map to utility classes:
|
||||||
|
|
||||||
|
| Namespace | Utility Classes |
|
||||||
|
|-----------|----------------|
|
||||||
|
| `--color-*` | `bg-red-500`, `text-sky-300`, `border-indigo-600`, etc. |
|
||||||
|
| `--font-*` | `font-sans`, `font-serif`, `font-mono` |
|
||||||
|
| `--breakpoint-*` | Responsive variants like `md:`, `lg:`, `xl:` |
|
||||||
|
| `--spacing-*` | Spacing scale for padding, margin, gap utilities |
|
||||||
|
| `--shadow-*` | `shadow-sm`, `shadow-md`, `shadow-lg` |
|
||||||
|
| `--ease-*` | Transition timing functions |
|
||||||
|
|
||||||
|
## Extending the Default Theme
|
||||||
|
|
||||||
|
Add new theme variables to extend the default theme:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
/* Add new color */
|
||||||
|
--color-brand-500: oklch(0.65 0.2 250);
|
||||||
|
|
||||||
|
/* Add new breakpoint */
|
||||||
|
--breakpoint-3xl: 120rem;
|
||||||
|
|
||||||
|
/* Add new font */
|
||||||
|
--font-display: "Satoshi", "sans-serif";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using Theme Variables
|
||||||
|
|
||||||
|
Tailwind generates CSS variables for your theme variables, so you can reference them:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Use utility classes -->
|
||||||
|
<div class="bg-brand-500 text-display">Content</div>
|
||||||
|
|
||||||
|
<!-- Reference CSS variables directly -->
|
||||||
|
<div style="background-color: var(--color-brand-500)">
|
||||||
|
Content
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Default Theme
|
||||||
|
|
||||||
|
When you import `tailwindcss`, it includes default theme variables:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@layer theme, base, components, utilities;
|
||||||
|
|
||||||
|
@import "./theme.css" layer(theme);
|
||||||
|
@import "./preflight.css" layer(base);
|
||||||
|
@import "./utilities.css" layer(utilities);
|
||||||
|
```
|
||||||
|
|
||||||
|
The default theme includes:
|
||||||
|
- Color palette (red, blue, green, etc. with 50-950 shades)
|
||||||
|
- Font families (sans, serif, mono)
|
||||||
|
- Spacing scale
|
||||||
|
- Shadows
|
||||||
|
- Breakpoints (sm, md, lg, xl, 2xl)
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Theme variables define which utilities exist in your project
|
||||||
|
- Use `@theme` directive to define design tokens
|
||||||
|
- Variables must be defined top-level, not nested
|
||||||
|
- Tailwind generates both utilities and CSS variables
|
||||||
|
- Default theme provides a solid starting point
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/theme
|
||||||
|
-->
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
---
|
||||||
|
name: core-utility-classes
|
||||||
|
description: Understanding Tailwind's utility-first approach and how to style elements with utility classes
|
||||||
|
---
|
||||||
|
|
||||||
|
# Utility Classes
|
||||||
|
|
||||||
|
Tailwind CSS uses a utility-first approach where you style elements by combining many single-purpose utility classes directly in your markup.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Instead of writing custom CSS, you compose designs using utility classes:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="mx-auto flex max-w-sm items-center gap-x-4 rounded-xl bg-white p-6 shadow-lg">
|
||||||
|
<img class="size-12 shrink-0" src="/logo.svg" alt="Logo" />
|
||||||
|
<div>
|
||||||
|
<div class="text-xl font-medium text-black">ChitChat</div>
|
||||||
|
<p class="text-gray-500">You have a new message!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
- **Faster development** - No need to come up with class names or switch between HTML and CSS files
|
||||||
|
- **Safer changes** - Adding or removing utilities only affects that element
|
||||||
|
- **Easier maintenance** - Find the element and change classes, no need to remember custom CSS
|
||||||
|
- **More portable** - Copy entire UI chunks between projects easily
|
||||||
|
- **Smaller CSS** - Utilities are reusable, so CSS doesn't grow linearly
|
||||||
|
|
||||||
|
## Why Not Inline Styles?
|
||||||
|
|
||||||
|
Utility classes have important advantages over inline styles:
|
||||||
|
|
||||||
|
- **Design constraints** - Choose from a predefined design system instead of magic numbers
|
||||||
|
- **State variants** - Target hover, focus, and other states with variants like `hover:bg-blue-600`
|
||||||
|
- **Responsive design** - Use responsive variants like `md:flex` for media queries
|
||||||
|
|
||||||
|
## Utility Class Structure
|
||||||
|
|
||||||
|
Utility classes follow consistent naming patterns:
|
||||||
|
|
||||||
|
- **Property-value**: `bg-blue-500`, `text-lg`, `rounded-xl`
|
||||||
|
- **Responsive**: `md:flex`, `lg:text-center`
|
||||||
|
- **State variants**: `hover:bg-blue-600`, `focus:outline-2`
|
||||||
|
- **Arbitrary values**: `top-[117px]`, `bg-[#bada55]`
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Every utility class is single-purpose and composable
|
||||||
|
- Utilities can be combined with variants for conditional styling
|
||||||
|
- Use complete class names - Tailwind scans your files as plain text
|
||||||
|
- Avoid dynamically constructing class names with string interpolation
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/styling-with-utility-classes
|
||||||
|
-->
|
||||||
156
.agents/skills/tailwindcss/references/core-variants.md
Normal file
156
.agents/skills/tailwindcss/references/core-variants.md
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
---
|
||||||
|
name: core-variants
|
||||||
|
description: Using variants to apply utilities conditionally based on states, pseudo-classes, and media queries
|
||||||
|
---
|
||||||
|
|
||||||
|
# Variants
|
||||||
|
|
||||||
|
Variants let you apply utility classes conditionally based on states, pseudo-classes, pseudo-elements, media queries, and more.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Add a variant prefix to any utility class to apply it conditionally:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Apply bg-sky-700 only on hover -->
|
||||||
|
<button class="bg-sky-500 hover:bg-sky-700">Save</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
Variants can be stacked to target specific situations:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Dark mode, medium breakpoint, on hover -->
|
||||||
|
<button class="dark:md:hover:bg-fuchsia-600">Save</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pseudo-Class Variants
|
||||||
|
|
||||||
|
### Interactive States
|
||||||
|
|
||||||
|
```html
|
||||||
|
<button class="bg-violet-500 hover:bg-violet-600 focus:outline-2 focus:outline-violet-500 active:bg-violet-700">
|
||||||
|
Save changes
|
||||||
|
</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
Common interactive variants:
|
||||||
|
- `hover:` - `:hover` pseudo-class
|
||||||
|
- `focus:` - `:focus` pseudo-class
|
||||||
|
- `active:` - `:active` pseudo-class
|
||||||
|
- `focus-visible:` - `:focus-visible`
|
||||||
|
- `focus-within:` - `:focus-within`
|
||||||
|
- `visited:` - `:visited`
|
||||||
|
|
||||||
|
### Structural Variants
|
||||||
|
|
||||||
|
```html
|
||||||
|
<ul>
|
||||||
|
<li class="py-4 first:pt-0 last:pb-0">Item 1</li>
|
||||||
|
<li class="py-4 first:pt-0 last:pb-0">Item 2</li>
|
||||||
|
<li class="py-4 first:pt-0 last:pb-0">Item 3</li>
|
||||||
|
</ul>
|
||||||
|
```
|
||||||
|
|
||||||
|
Common structural variants:
|
||||||
|
- `first:` - `:first-child`
|
||||||
|
- `last:` - `:last-child`
|
||||||
|
- `odd:` - `:nth-child(odd)`
|
||||||
|
- `even:` - `:nth-child(even)`
|
||||||
|
- `only:` - `:only-child`
|
||||||
|
|
||||||
|
### Form States
|
||||||
|
|
||||||
|
```html
|
||||||
|
<input class="border-gray-300 required:border-red-500 invalid:border-red-500" />
|
||||||
|
```
|
||||||
|
|
||||||
|
Common form variants:
|
||||||
|
- `required:` - `:required`
|
||||||
|
- `optional:` - `:optional`
|
||||||
|
- `invalid:` - `:invalid`
|
||||||
|
- `valid:` - `:valid`
|
||||||
|
- `disabled:` - `:disabled`
|
||||||
|
- `enabled:` - `:enabled`
|
||||||
|
- `checked:` - `:checked`
|
||||||
|
|
||||||
|
## Pseudo-Element Variants
|
||||||
|
|
||||||
|
```html
|
||||||
|
<input class="placeholder:text-gray-400 before:content-['*'] after:content-['required']" />
|
||||||
|
```
|
||||||
|
|
||||||
|
Common pseudo-element variants:
|
||||||
|
- `before:` - `::before`
|
||||||
|
- `after:` - `::after`
|
||||||
|
- `placeholder:` - `::placeholder`
|
||||||
|
- `selection:` - `::selection`
|
||||||
|
- `first-line:` - `::first-line`
|
||||||
|
- `first-letter:` - `::first-letter`
|
||||||
|
|
||||||
|
## Media Query Variants
|
||||||
|
|
||||||
|
### Responsive Variants
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="text-sm md:text-base lg:text-lg">Responsive text</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dark Mode
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="bg-white dark:bg-gray-800 text-gray-900 dark:text-white">
|
||||||
|
Content
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
By default uses `prefers-color-scheme`, but can be customized to use a class or data attribute.
|
||||||
|
|
||||||
|
### Reduced Motion
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="transition-all motion-reduce:transition-none">
|
||||||
|
Animated content
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Attribute Selector Variants
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- RTL support -->
|
||||||
|
<div class="text-left rtl:text-right">Content</div>
|
||||||
|
|
||||||
|
<!-- Open state -->
|
||||||
|
<details class="[&[open]]:bg-gray-100">Details</details>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Arbitrary Variants
|
||||||
|
|
||||||
|
Use arbitrary variants for custom selectors:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<ul>
|
||||||
|
<li class="lg:[&:nth-child(-n+3)]:hover:underline">Item</li>
|
||||||
|
</ul>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Child Selector Variants
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="[&>*]:mb-4 [&>*:last-child]:mb-0">
|
||||||
|
<p>Paragraph 1</p>
|
||||||
|
<p>Paragraph 2</p>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Variants prefix utilities to apply them conditionally
|
||||||
|
- Variants can be stacked: `dark:md:hover:bg-blue-600`
|
||||||
|
- Use variants for states, pseudo-classes, media queries, and more
|
||||||
|
- Arbitrary variants enable custom selector patterns
|
||||||
|
- Child selector variants target descendant elements
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/hover-focus-and-other-states
|
||||||
|
-->
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
---
|
||||||
|
name: effects-form-controls
|
||||||
|
description: Form control styling with accent-color, appearance, caret-color, and resize
|
||||||
|
---
|
||||||
|
|
||||||
|
# Form Controls & Input Styling
|
||||||
|
|
||||||
|
Utilities for styling form controls: accent color, native appearance, caret color, and resize behavior.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Accent color
|
||||||
|
|
||||||
|
Control the accent color of checkboxes, radio buttons, range inputs, and progress:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<input type="checkbox" class="accent-pink-500" checked />
|
||||||
|
<input type="radio" class="accent-rose-500" name="opt" />
|
||||||
|
<input type="range" class="accent-indigo-500" />
|
||||||
|
|
||||||
|
<!-- Opacity modifier -->
|
||||||
|
<input type="checkbox" class="accent-purple-500/75" />
|
||||||
|
```
|
||||||
|
|
||||||
|
### Appearance
|
||||||
|
|
||||||
|
Remove native form control styling for custom designs:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Custom select with overlay icon -->
|
||||||
|
<div class="grid">
|
||||||
|
<select class="col-start-1 row-start-1 appearance-none bg-gray-50 ...">
|
||||||
|
<option>Yes</option>
|
||||||
|
</select>
|
||||||
|
<svg class="pointer-events-none col-start-1 row-start-1 ...">...</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Restore default in forced-colors mode (accessibility) -->
|
||||||
|
<input type="checkbox" class="appearance-none forced-colors:appearance-auto" />
|
||||||
|
```
|
||||||
|
|
||||||
|
### Caret color
|
||||||
|
|
||||||
|
Set the text input cursor color:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<textarea class="caret-pink-500">Focus to see caret</textarea>
|
||||||
|
<input class="caret-blue-500" />
|
||||||
|
```
|
||||||
|
|
||||||
|
### Resize
|
||||||
|
|
||||||
|
Control textarea resize behavior:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<textarea class="resize-y">Vertical only</textarea>
|
||||||
|
<textarea class="resize-x">Horizontal only</textarea>
|
||||||
|
<textarea class="resize">Both directions</textarea>
|
||||||
|
<textarea class="resize-none">No resize handle</textarea>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- `accent-*` - theme colors for checkboxes, radio, range; use `accent-[#hex]` or `accent-(--var)` for custom
|
||||||
|
- `appearance-none` - remove native styling (custom selects, checkboxes)
|
||||||
|
- `appearance-auto` - restore default (e.g. for `forced-colors: active`)
|
||||||
|
- `caret-*` - theme colors for input cursor; matches text color patterns
|
||||||
|
- `resize` - both; `resize-x` - horizontal; `resize-y` - vertical; `resize-none` - no handle
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/accent-color
|
||||||
|
- https://tailwindcss.com/docs/appearance
|
||||||
|
- https://tailwindcss.com/docs/caret-color
|
||||||
|
- https://tailwindcss.com/docs/resize
|
||||||
|
-->
|
||||||
59
.agents/skills/tailwindcss/references/effects-scroll-snap.md
Normal file
59
.agents/skills/tailwindcss/references/effects-scroll-snap.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
---
|
||||||
|
name: effects-scroll-snap
|
||||||
|
description: CSS scroll snap for carousels and scroll containers
|
||||||
|
---
|
||||||
|
|
||||||
|
# Scroll Snap
|
||||||
|
|
||||||
|
Utilities for scroll snap behavior in overflow containers. Use for carousels, horizontal galleries, or paged scroll.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Container
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Horizontal snap (carousel) -->
|
||||||
|
<div class="snap-x snap-mandatory overflow-x-auto flex gap-4">
|
||||||
|
<div class="snap-center shrink-0 w-80">Slide 1</div>
|
||||||
|
<div class="snap-center shrink-0 w-80">Slide 2</div>
|
||||||
|
<div class="snap-center shrink-0 w-80">Slide 3</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Vertical snap -->
|
||||||
|
<div class="snap-y snap-mandatory overflow-y-auto h-screen">
|
||||||
|
<div class="snap-start h-screen">Section 1</div>
|
||||||
|
<div class="snap-start h-screen">Section 2</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Both axes -->
|
||||||
|
<div class="snap-both overflow-auto">...</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Strictness
|
||||||
|
|
||||||
|
- `snap-mandatory` - always rest on a snap point
|
||||||
|
- `snap-proximity` - snap only when close to a point (default)
|
||||||
|
|
||||||
|
### Child alignment
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="snap-x overflow-x-auto">
|
||||||
|
<div class="snap-center">Center snap</div>
|
||||||
|
<div class="snap-start">Start snap</div>
|
||||||
|
<div class="snap-end">End snap</div>
|
||||||
|
<div class="snap-align-none">No snap</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- `snap-x` - horizontal; `snap-y` - vertical; `snap-both` - both; `snap-none` - disable
|
||||||
|
- `snap-mandatory` / `snap-proximity` - strictness
|
||||||
|
- Child: `snap-center`, `snap-start`, `snap-end`, `snap-align-none`
|
||||||
|
- Requires overflow (e.g. `overflow-x-auto`) and scroll on container
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/scroll-snap-type
|
||||||
|
- https://tailwindcss.com/docs/scroll-snap-align
|
||||||
|
-->
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
---
|
||||||
|
name: effects-transition-animation
|
||||||
|
description: CSS transitions, animation keyframes, and reduced motion support
|
||||||
|
---
|
||||||
|
|
||||||
|
# Transition & Animation
|
||||||
|
|
||||||
|
Utilities for CSS transitions and animations.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Transition property
|
||||||
|
|
||||||
|
```html
|
||||||
|
<button class="transition hover:bg-blue-600">Transitions common properties</button>
|
||||||
|
<button class="transition-all hover:scale-110">All properties</button>
|
||||||
|
<button class="transition-colors hover:bg-indigo-500">Colors only</button>
|
||||||
|
<button class="transition-opacity hover:opacity-75">Opacity only</button>
|
||||||
|
<button class="transition-transform hover:translate-y-1">Transform only</button>
|
||||||
|
<button class="transition-none">No transition</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Transition duration and delay
|
||||||
|
|
||||||
|
```html
|
||||||
|
<button class="transition duration-150">150ms (default)</button>
|
||||||
|
<button class="transition duration-300">300ms</button>
|
||||||
|
<button class="transition duration-500">500ms</button>
|
||||||
|
<button class="transition delay-150">Delay 150ms</button>
|
||||||
|
<button class="transition duration-300 delay-100">Both</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Transition timing
|
||||||
|
|
||||||
|
```html
|
||||||
|
<button class="transition ease-linear">Linear</button>
|
||||||
|
<button class="transition ease-in">Ease in</button>
|
||||||
|
<button class="transition ease-out">Ease out</button>
|
||||||
|
<button class="transition ease-in-out">Ease in-out (default)</button>
|
||||||
|
<button class="transition ease-[cubic-bezier(0.4,0,0.2,1)]">Arbitrary</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Animation keyframes
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="animate-spin">Spinning</div>
|
||||||
|
<div class="animate-ping">Ping effect</div>
|
||||||
|
<div class="animate-pulse">Pulse</div>
|
||||||
|
<div class="animate-bounce">Bounce</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Built-in: `animate-spin`, `animate-ping`, `animate-pulse`, `animate-bounce`. Use `@keyframes` in custom CSS for more.
|
||||||
|
|
||||||
|
### Reduced motion
|
||||||
|
|
||||||
|
```html
|
||||||
|
<button class="transition hover:-translate-y-1 motion-reduce:transition-none motion-reduce:hover:translate-y-0">
|
||||||
|
Respects prefers-reduced-motion
|
||||||
|
</button>
|
||||||
|
<div class="animate-spin motion-reduce:animate-none">Spinner hidden when reduced motion</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `motion-reduce:` to disable or simplify animations when user prefers reduced motion.
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Transition: `transition`, `transition-all`, `transition-colors`, `transition-opacity`, `transition-transform`
|
||||||
|
- Duration: `duration-{75,100,150,200,300,500,700,1000}`
|
||||||
|
- Delay: `delay-{75,100,150,200,300,500,700,1000}`
|
||||||
|
- Timing: `ease-{linear,in,in-out,out}`
|
||||||
|
- Animation: `animate-spin`, `animate-ping`, `animate-pulse`, `animate-bounce`
|
||||||
|
- Always consider `motion-reduce:` for accessibility
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/transition-property
|
||||||
|
- https://tailwindcss.com/docs/transition-duration
|
||||||
|
- https://tailwindcss.com/docs/transition-timing-function
|
||||||
|
- https://tailwindcss.com/docs/animation
|
||||||
|
-->
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
---
|
||||||
|
name: effects-visibility-interactivity
|
||||||
|
description: Visibility, cursor, pointer-events, user-select, and z-index
|
||||||
|
---
|
||||||
|
|
||||||
|
# Visibility & Interactivity
|
||||||
|
|
||||||
|
Utilities for visibility, cursor, pointer-events, user-select, and stacking.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Visibility
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="visible">Visible (default)</div>
|
||||||
|
<div class="invisible">Hidden but in layout</div>
|
||||||
|
<div class="collapse">Table collapse</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `invisible` when you need to keep layout space; use `hidden` (display:none) to remove from flow.
|
||||||
|
|
||||||
|
### Cursor
|
||||||
|
|
||||||
|
```html
|
||||||
|
<button class="cursor-pointer">Pointer</button>
|
||||||
|
<div class="cursor-not-allowed">Disabled</div>
|
||||||
|
<div class="cursor-wait">Loading</div>
|
||||||
|
<div class="cursor-move">Draggable</div>
|
||||||
|
<div class="cursor-grab">Grab</div>
|
||||||
|
<div class="cursor-grabbing">Grabbing</div>
|
||||||
|
<div class="cursor-text">Text select</div>
|
||||||
|
<div class="cursor-default">Default</div>
|
||||||
|
<div class="cursor-none">No cursor</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pointer events
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="pointer-events-none">Ignore all pointer events</div>
|
||||||
|
<div class="pointer-events-auto">Default behavior</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Useful for overlays: make overlay `pointer-events-none` so clicks pass through, or `pointer-events-none` on disabled elements.
|
||||||
|
|
||||||
|
### User select
|
||||||
|
|
||||||
|
```html
|
||||||
|
<p class="select-none">Cannot select</p>
|
||||||
|
<p class="select-text">Select text (default)</p>
|
||||||
|
<p class="select-all">Select all on click</p>
|
||||||
|
<p class="select-auto">Browser default</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Z-index
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="z-0">0</div>
|
||||||
|
<div class="z-10">10</div>
|
||||||
|
<div class="z-20">20</div>
|
||||||
|
<div class="z-50">50</div>
|
||||||
|
<div class="z-auto">Auto</div>
|
||||||
|
<div class="z-[100]">Arbitrary</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Common: `z-0` (base), `z-10` (dropdowns), `z-20` (fixed nav), `z-50` (modal), `z-40` (overlay).
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Visibility: `visible`, `invisible`, `collapse`
|
||||||
|
- Cursor: `cursor-{pointer,not-allowed,wait,move,grab,text,default,none}`
|
||||||
|
- Pointer events: `pointer-events-none`, `pointer-events-auto`
|
||||||
|
- User select: `select-none`, `select-text`, `select-all`
|
||||||
|
- Z-index: `z-{0,10,20,30,40,50,auto}`, `z-[n]`
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/visibility
|
||||||
|
- https://tailwindcss.com/docs/cursor
|
||||||
|
- https://tailwindcss.com/docs/pointer-events
|
||||||
|
- https://tailwindcss.com/docs/user-select
|
||||||
|
- https://tailwindcss.com/docs/z-index
|
||||||
|
-->
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
---
|
||||||
|
name: features-content-detection
|
||||||
|
description: How Tailwind detects classes in source files and how to customize content scanning
|
||||||
|
---
|
||||||
|
|
||||||
|
# Detecting Classes in Source Files
|
||||||
|
|
||||||
|
Tailwind scans your project for utility classes and generates CSS based on what you've actually used.
|
||||||
|
|
||||||
|
## How Classes Are Detected
|
||||||
|
|
||||||
|
Tailwind treats all source files as plain text and looks for tokens that could be class names:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
export function Button({ color, children }) {
|
||||||
|
const colors = {
|
||||||
|
black: "bg-black text-white",
|
||||||
|
blue: "bg-blue-500 text-white",
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button className={`${colors[color]} rounded-full px-2 py-1.5`}>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Tailwind detects `bg-black`, `text-white`, `bg-blue-500`, `rounded-full`, `px-2`, and `py-1.5` from this file.
|
||||||
|
|
||||||
|
## Dynamic Class Names
|
||||||
|
|
||||||
|
Tailwind scans files as plain text, so it can't understand string concatenation or interpolation.
|
||||||
|
|
||||||
|
### ❌ Don't Construct Classes Dynamically
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="text-{{ error ? 'red' : 'green' }}-600"></div>
|
||||||
|
```
|
||||||
|
|
||||||
|
The strings `text-red-600` and `text-green-600` don't exist in the file, so Tailwind won't generate them.
|
||||||
|
|
||||||
|
### ✅ Use Complete Class Names
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="{{ error ? 'text-red-600' : 'text-green-600' }}">
|
||||||
|
Content
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### ❌ Don't Build Classes from Props
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
function Button({ color, children }) {
|
||||||
|
return <button className={`bg-${color}-600 hover:bg-${color}-500`}>{children}</button>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ✅ Map Props to Static Classes
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
function Button({ color, children }) {
|
||||||
|
const colorVariants = {
|
||||||
|
blue: "bg-blue-600 hover:bg-blue-500",
|
||||||
|
red: "bg-red-600 hover:bg-red-500",
|
||||||
|
};
|
||||||
|
|
||||||
|
return <button className={`${colorVariants[color]} ...`}>{children}</button>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Which Files Are Scanned
|
||||||
|
|
||||||
|
Tailwind scans every file in your project except:
|
||||||
|
|
||||||
|
- Files in `.gitignore`
|
||||||
|
- Files in `node_modules`
|
||||||
|
- Binary files (images, videos, zip files)
|
||||||
|
- CSS files
|
||||||
|
- Common package manager lock files
|
||||||
|
|
||||||
|
## Explicitly Registering Sources
|
||||||
|
|
||||||
|
Use `@source` to explicitly register source paths:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@import "tailwindcss";
|
||||||
|
@source "../node_modules/@acmecorp/ui-lib";
|
||||||
|
```
|
||||||
|
|
||||||
|
This is useful for external libraries built with Tailwind that are in `.gitignore`.
|
||||||
|
|
||||||
|
## Setting Base Path
|
||||||
|
|
||||||
|
Set the base path for source detection:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@import "tailwindcss" source("../src");
|
||||||
|
```
|
||||||
|
|
||||||
|
Useful in monorepos where build commands run from the root.
|
||||||
|
|
||||||
|
## Ignoring Specific Paths
|
||||||
|
|
||||||
|
Use `@source not` to ignore paths:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@import "tailwindcss";
|
||||||
|
@source not "../src/components/legacy";
|
||||||
|
```
|
||||||
|
|
||||||
|
## Disabling Automatic Detection
|
||||||
|
|
||||||
|
Use `source(none)` to disable automatic detection:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@import "tailwindcss" source(none);
|
||||||
|
|
||||||
|
@source "../admin";
|
||||||
|
@source "../shared";
|
||||||
|
```
|
||||||
|
|
||||||
|
Useful for projects with multiple Tailwind stylesheets.
|
||||||
|
|
||||||
|
## Safelisting Utilities
|
||||||
|
|
||||||
|
Force Tailwind to generate specific classes with `@source inline()`:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@import "tailwindcss";
|
||||||
|
@source inline("underline");
|
||||||
|
```
|
||||||
|
|
||||||
|
### Safelisting with Variants
|
||||||
|
|
||||||
|
Generate classes with variants:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@import "tailwindcss";
|
||||||
|
@source inline("{hover:,focus:,}underline");
|
||||||
|
```
|
||||||
|
|
||||||
|
### Safelisting with Ranges
|
||||||
|
|
||||||
|
Use brace expansion to generate multiple classes:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@import "tailwindcss";
|
||||||
|
@source inline("{hover:,}bg-red-{50,{100..900..100},950}");
|
||||||
|
```
|
||||||
|
|
||||||
|
This generates `bg-red-50` through `bg-red-950` with hover variants.
|
||||||
|
|
||||||
|
## Explicitly Excluding Classes
|
||||||
|
|
||||||
|
Use `@source not inline()` to prevent specific classes from being generated:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@import "tailwindcss";
|
||||||
|
@source not inline("{hover:,focus:,}bg-red-{50,{100..900..100},950}");
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Tailwind scans files as plain text
|
||||||
|
- Always use complete class names, never construct them dynamically
|
||||||
|
- Map props/state to static class names
|
||||||
|
- Use `@source` to explicitly register or ignore paths
|
||||||
|
- Use `@source inline()` to safelist utilities
|
||||||
|
- Brace expansion works in inline sources for ranges
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/detecting-classes-in-source-files
|
||||||
|
-->
|
||||||
203
.agents/skills/tailwindcss/references/features-custom-styles.md
Normal file
203
.agents/skills/tailwindcss/references/features-custom-styles.md
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
---
|
||||||
|
name: features-custom-styles
|
||||||
|
description: Adding custom styles, utilities, variants, and working with arbitrary values
|
||||||
|
---
|
||||||
|
|
||||||
|
# Adding Custom Styles
|
||||||
|
|
||||||
|
Tailwind is designed to be extensible. This guide covers customizing your theme, using arbitrary values, adding custom CSS, and extending the framework.
|
||||||
|
|
||||||
|
## Customizing Your Theme
|
||||||
|
|
||||||
|
Add custom design tokens using `@theme`:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--font-display: "Satoshi", "sans-serif";
|
||||||
|
--breakpoint-3xl: 120rem;
|
||||||
|
--color-brand-500: oklch(0.65 0.2 250);
|
||||||
|
--ease-fluid: cubic-bezier(0.3, 0, 0, 1);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Arbitrary Values
|
||||||
|
|
||||||
|
Use square bracket notation for one-off values that don't belong in your theme:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Arbitrary values -->
|
||||||
|
<div class="top-[117px] bg-[#bada55] text-[22px]">
|
||||||
|
Content
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- With variants -->
|
||||||
|
<div class="top-[117px] lg:top-[344px]">
|
||||||
|
Content
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### CSS Variables as Arbitrary Values
|
||||||
|
|
||||||
|
Reference CSS variables:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="fill-(--my-brand-color)">
|
||||||
|
Content
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
This is shorthand for `fill-[var(--my-brand-color)]`.
|
||||||
|
|
||||||
|
## Arbitrary Properties
|
||||||
|
|
||||||
|
Use square brackets for CSS properties Tailwind doesn't have utilities for:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="[mask-type:luminance]">
|
||||||
|
Content
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- With variants -->
|
||||||
|
<div class="[mask-type:luminance] hover:[mask-type:alpha]">
|
||||||
|
Content
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### CSS Variables
|
||||||
|
|
||||||
|
Set CSS variables with arbitrary properties:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="[--scroll-offset:56px] lg:[--scroll-offset:44px]">
|
||||||
|
Content
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Arbitrary Variants
|
||||||
|
|
||||||
|
Create custom selectors on the fly:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<ul>
|
||||||
|
<li class="lg:[&:nth-child(-n+3)]:hover:underline">Item</li>
|
||||||
|
</ul>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Handling Whitespace
|
||||||
|
|
||||||
|
Use underscores for spaces in arbitrary values:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="grid-cols-[1fr_500px_2fr]">
|
||||||
|
Content
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Tailwind converts underscores to spaces, except in contexts where underscores are valid (like URLs).
|
||||||
|
|
||||||
|
## Custom Utilities
|
||||||
|
|
||||||
|
Add custom utilities with `@utility`:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@utility tab-4 {
|
||||||
|
tab-size: 4;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you can use `tab-4` utility class, and it works with variants:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="tab-4 hover:tab-8">
|
||||||
|
Content
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom Variants
|
||||||
|
|
||||||
|
Add custom variants with `@custom-variant`:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@custom-variant theme-midnight (&:where([data-theme="midnight"] *));
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you can use `theme-midnight:` variant:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="theme-midnight:bg-black theme-midnight:text-white">
|
||||||
|
Content
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using Variants in CSS
|
||||||
|
|
||||||
|
Apply Tailwind variants to custom CSS with `@variant`:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.my-element {
|
||||||
|
background: white;
|
||||||
|
|
||||||
|
@variant dark {
|
||||||
|
background: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
@variant hover {
|
||||||
|
background: gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Base Styles
|
||||||
|
|
||||||
|
Add base styles to the `base` layer:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@layer base {
|
||||||
|
h1 {
|
||||||
|
font-size: var(--text-2xl);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: var(--text-xl);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-blue-600);
|
||||||
|
text-decoration-line: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Component Styles
|
||||||
|
|
||||||
|
Add component styles to the `components` layer:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@layer components {
|
||||||
|
.btn-primary {
|
||||||
|
@apply bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Use `@theme` to customize design tokens
|
||||||
|
- Arbitrary values with `[]` for one-off values
|
||||||
|
- Arbitrary properties for CSS properties without utilities
|
||||||
|
- `@utility` for custom utilities
|
||||||
|
- `@custom-variant` for custom variants
|
||||||
|
- `@layer` for organizing base and component styles
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/adding-custom-styles
|
||||||
|
-->
|
||||||
137
.agents/skills/tailwindcss/references/features-dark-mode.md
Normal file
137
.agents/skills/tailwindcss/references/features-dark-mode.md
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
---
|
||||||
|
name: features-dark-mode
|
||||||
|
description: Implementing dark mode with Tailwind's dark variant and custom dark mode strategies
|
||||||
|
---
|
||||||
|
|
||||||
|
# Dark Mode
|
||||||
|
|
||||||
|
Tailwind includes a `dark` variant that lets you style your site differently when dark mode is enabled.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Use the `dark:` variant to apply styles in dark mode:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="bg-white dark:bg-gray-800 text-gray-900 dark:text-white">
|
||||||
|
Content
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Default Behavior
|
||||||
|
|
||||||
|
By default, the `dark` variant uses the `prefers-color-scheme` CSS media feature:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.dark\:bg-gray-800 {
|
||||||
|
background-color: rgb(31 41 55);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manual Toggle with Class
|
||||||
|
|
||||||
|
Override the `dark` variant to use a class selector:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@custom-variant dark (&:where(.dark, .dark *));
|
||||||
|
```
|
||||||
|
|
||||||
|
Now dark mode utilities apply when the `dark` class is present:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<html class="dark">
|
||||||
|
<body>
|
||||||
|
<div class="bg-white dark:bg-black">Content</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manual Toggle with Data Attribute
|
||||||
|
|
||||||
|
Use a data attribute instead:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
|
||||||
|
```
|
||||||
|
|
||||||
|
```html
|
||||||
|
<html data-theme="dark">
|
||||||
|
<body>
|
||||||
|
<div class="bg-white dark:bg-black">Content</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Three-Way Theme Toggle
|
||||||
|
|
||||||
|
Support light mode, dark mode, and system preference:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// On page load
|
||||||
|
document.documentElement.classList.toggle(
|
||||||
|
"dark",
|
||||||
|
localStorage.theme === "dark" ||
|
||||||
|
(!("theme" in localStorage) &&
|
||||||
|
window.matchMedia("(prefers-color-scheme: dark)").matches)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set light mode
|
||||||
|
localStorage.theme = "light";
|
||||||
|
document.documentElement.classList.remove("dark");
|
||||||
|
|
||||||
|
// Set dark mode
|
||||||
|
localStorage.theme = "dark";
|
||||||
|
document.documentElement.classList.add("dark");
|
||||||
|
|
||||||
|
// Respect system preference
|
||||||
|
localStorage.removeItem("theme");
|
||||||
|
document.documentElement.classList.toggle(
|
||||||
|
"dark",
|
||||||
|
window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Cards
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg dark:shadow-none">
|
||||||
|
<h2 class="text-gray-900 dark:text-white">Title</h2>
|
||||||
|
<p class="text-gray-500 dark:text-gray-400">Description</p>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Borders
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="border border-gray-200 dark:border-gray-700">
|
||||||
|
Content
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Buttons
|
||||||
|
|
||||||
|
```html
|
||||||
|
<button class="bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700 text-white">
|
||||||
|
Button
|
||||||
|
</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Use `dark:` variant for dark mode styles
|
||||||
|
- Default uses `prefers-color-scheme` media query
|
||||||
|
- Override with `@custom-variant` for manual toggles
|
||||||
|
- Can use class or data attribute selectors
|
||||||
|
- Combine with responsive variants: `dark:md:bg-gray-800`
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/dark-mode
|
||||||
|
-->
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
---
|
||||||
|
name: features-functions-directives
|
||||||
|
description: Tailwind's CSS directives (@import, @theme, @utility, @variant) and functions (--alpha, --spacing)
|
||||||
|
---
|
||||||
|
|
||||||
|
# Functions and Directives
|
||||||
|
|
||||||
|
Tailwind provides custom CSS directives and functions for working with your design system.
|
||||||
|
|
||||||
|
## Directives
|
||||||
|
|
||||||
|
Directives are custom Tailwind-specific at-rules that offer special functionality.
|
||||||
|
|
||||||
|
### @import
|
||||||
|
|
||||||
|
Import CSS files, including Tailwind:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@import "tailwindcss";
|
||||||
|
```
|
||||||
|
|
||||||
|
### @theme
|
||||||
|
|
||||||
|
Define your project's design tokens:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@theme {
|
||||||
|
--font-display: "Satoshi", "sans-serif";
|
||||||
|
--breakpoint-3xl: 120rem;
|
||||||
|
--color-brand-500: oklch(0.65 0.2 250);
|
||||||
|
--ease-fluid: cubic-bezier(0.3, 0, 0, 1);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### @source
|
||||||
|
|
||||||
|
Explicitly specify source files for class detection:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@import "tailwindcss";
|
||||||
|
@source "../node_modules/@my-company/ui-lib";
|
||||||
|
```
|
||||||
|
|
||||||
|
### @utility
|
||||||
|
|
||||||
|
Add custom utilities:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@utility tab-4 {
|
||||||
|
tab-size: 4;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Custom utilities work with variants:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="tab-4 hover:tab-8">Content</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### @variant
|
||||||
|
|
||||||
|
Apply Tailwind variants to styles in your CSS:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.my-element {
|
||||||
|
background: white;
|
||||||
|
|
||||||
|
@variant dark {
|
||||||
|
background: black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### @custom-variant
|
||||||
|
|
||||||
|
Add custom variants:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@custom-variant theme-midnight (&:where([data-theme="midnight"] *));
|
||||||
|
```
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="theme-midnight:bg-black">Content</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### @apply
|
||||||
|
|
||||||
|
Inline existing utility classes into custom CSS:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.select2-dropdown {
|
||||||
|
@apply rounded-b-lg shadow-md;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-search {
|
||||||
|
@apply rounded border border-gray-300;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### @reference
|
||||||
|
|
||||||
|
Import stylesheet for reference without including styles (useful for Vue/Svelte components):
|
||||||
|
|
||||||
|
```html
|
||||||
|
<style>
|
||||||
|
@reference "../../app.css";
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
@apply text-2xl font-bold text-red-500;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
Or reference Tailwind directly:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<style>
|
||||||
|
@reference "tailwindcss";
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
@apply text-2xl font-bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Subpath Imports
|
||||||
|
|
||||||
|
Directives support subpath imports (like TypeScript path aliases):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"#app.css": "./src/css/app.css"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```html
|
||||||
|
<style>
|
||||||
|
@reference "#app.css";
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
@apply text-2xl font-bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|
||||||
|
Tailwind provides build-time functions for working with colors and spacing.
|
||||||
|
|
||||||
|
### --alpha()
|
||||||
|
|
||||||
|
Adjust the opacity of a color:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.my-element {
|
||||||
|
color: --alpha(var(--color-lime-300) / 50%);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Compiles to:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.my-element {
|
||||||
|
color: color-mix(in oklab, var(--color-lime-300) 50%, transparent);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### --spacing()
|
||||||
|
|
||||||
|
Generate spacing values based on your theme:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.my-element {
|
||||||
|
margin: --spacing(4);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Compiles to:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.my-element {
|
||||||
|
margin: calc(var(--spacing) * 4);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Useful in arbitrary values with `calc()`:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="py-[calc(--spacing(4)-1px)]">
|
||||||
|
Content
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Compatibility Directives
|
||||||
|
|
||||||
|
For compatibility with Tailwind CSS v3.x:
|
||||||
|
|
||||||
|
### @config
|
||||||
|
|
||||||
|
Load a legacy JavaScript-based configuration:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@config "../../tailwind.config.js";
|
||||||
|
```
|
||||||
|
|
||||||
|
### @plugin
|
||||||
|
|
||||||
|
Load a legacy JavaScript-based plugin:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@plugin "@tailwindcss/typography";
|
||||||
|
```
|
||||||
|
|
||||||
|
### theme()
|
||||||
|
|
||||||
|
Access theme values using dot notation (deprecated):
|
||||||
|
|
||||||
|
```css
|
||||||
|
.my-element {
|
||||||
|
margin: theme(spacing.12);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Prefer using CSS theme variables instead.
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Directives are Tailwind-specific at-rules
|
||||||
|
- `@theme` defines design tokens
|
||||||
|
- `@utility` creates custom utilities
|
||||||
|
- `@custom-variant` creates custom variants
|
||||||
|
- `@apply` inlines utilities into CSS
|
||||||
|
- `--alpha()` and `--spacing()` are build-time functions
|
||||||
|
- Compatibility directives support v3.x configs
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/functions-and-directives
|
||||||
|
-->
|
||||||
91
.agents/skills/tailwindcss/references/features-upgrade.md
Normal file
91
.agents/skills/tailwindcss/references/features-upgrade.md
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
---
|
||||||
|
name: features-upgrade
|
||||||
|
description: Migrating from Tailwind CSS v3 to v4
|
||||||
|
---
|
||||||
|
|
||||||
|
# Upgrade Guide (v3 → v4)
|
||||||
|
|
||||||
|
Key changes when upgrading from Tailwind CSS v3 to v4. Use the automated upgrade tool when possible.
|
||||||
|
|
||||||
|
## Upgrade Tool
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx @tailwindcss/upgrade
|
||||||
|
```
|
||||||
|
|
||||||
|
Requires Node.js 20+. Run in a new branch, review diff, test. Handles most migration automatically.
|
||||||
|
|
||||||
|
## Installation Changes
|
||||||
|
|
||||||
|
- **PostCSS**: Use `@tailwindcss/postcss`; remove `postcss-import` and `autoprefixer` (handled by v4)
|
||||||
|
- **Vite**: Prefer `@tailwindcss/vite` over PostCSS
|
||||||
|
- **CLI**: Use `npx @tailwindcss/cli` instead of `npx tailwindcss`
|
||||||
|
|
||||||
|
## Import Change
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* v3 */
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
/* v4 */
|
||||||
|
@import "tailwindcss";
|
||||||
|
```
|
||||||
|
|
||||||
|
## Renamed Utilities
|
||||||
|
|
||||||
|
| v3 | v4 |
|
||||||
|
|----|-----|
|
||||||
|
| `shadow-sm` | `shadow-xs` |
|
||||||
|
| `shadow` | `shadow-sm` |
|
||||||
|
| `rounded-sm` | `rounded-xs` |
|
||||||
|
| `rounded` | `rounded-sm` |
|
||||||
|
| `blur-sm` | `blur-xs` |
|
||||||
|
| `blur` | `blur-sm` |
|
||||||
|
| `outline-none` | `outline-hidden` |
|
||||||
|
| `ring` | `ring-3` |
|
||||||
|
|
||||||
|
## Removed / Replaced
|
||||||
|
|
||||||
|
- `bg-opacity-*`, `text-opacity-*`, etc. → use `bg-black/50`, `text-black/50`
|
||||||
|
- `flex-shrink-*` → `shrink-*`
|
||||||
|
- `flex-grow-*` → `grow-*`
|
||||||
|
- `overflow-ellipsis` → `text-ellipsis`
|
||||||
|
|
||||||
|
## Important Modifier
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- v3: ! at start -->
|
||||||
|
<div class="!bg-red-500">
|
||||||
|
|
||||||
|
<!-- v4: ! at end -->
|
||||||
|
<div class="bg-red-500!">
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ring & Border Defaults
|
||||||
|
|
||||||
|
- `ring` width: 3px → 1px; use `ring-3` for v3 behavior
|
||||||
|
- `ring` / `border` default color: `currentColor` (was gray-200 / blue-500)
|
||||||
|
- Always specify color: `ring-3 ring-blue-500`, `border border-gray-200`
|
||||||
|
|
||||||
|
## Other Breaking Changes
|
||||||
|
|
||||||
|
- **Space/divide selectors**: Changed from `:not([hidden]) ~ :not([hidden])` to `:not(:last-child)`; may affect layout
|
||||||
|
- **Variant stacking**: Left-to-right in v4 (was right-to-left)
|
||||||
|
- **Transform reset**: `transform-none` no longer resets rotate/scale/translate; use `scale-none`, `rotate-none`, etc.
|
||||||
|
- **Hover on mobile**: `hover` only applies when device supports hover; override with `@custom-variant hover (&:hover)` if needed
|
||||||
|
- **Arbitrary values**: Use `bg-(--var)` not `bg-[--var]` for CSS variables
|
||||||
|
- **theme()**: Use `theme(--breakpoint-xl)` not `theme(screens.xl)`
|
||||||
|
- **@layer utilities/components**: Use `@utility` directive instead
|
||||||
|
- **corePlugins, safelist, separator**: Not supported; use `@source inline()` for safelisting
|
||||||
|
- **Sass/Less/Stylus**: v4 not designed for use with CSS preprocessors
|
||||||
|
|
||||||
|
## Browser Support
|
||||||
|
|
||||||
|
v4 targets Safari 16.4+, Chrome 111+, Firefox 128+. For older browsers, stay on v3.4.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/upgrade-guide
|
||||||
|
-->
|
||||||
39
.agents/skills/tailwindcss/references/layout-aspect-ratio.md
Normal file
39
.agents/skills/tailwindcss/references/layout-aspect-ratio.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
name: layout-aspect-ratio
|
||||||
|
description: Controlling element aspect ratio for responsive media
|
||||||
|
---
|
||||||
|
|
||||||
|
# Aspect Ratio
|
||||||
|
|
||||||
|
Utilities for controlling the aspect ratio of an element (e.g. video, images).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- 16:9 video container -->
|
||||||
|
<div class="aspect-video">
|
||||||
|
<iframe src="..."></iframe>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 4:3 -->
|
||||||
|
<div class="aspect-[4/3]">Content</div>
|
||||||
|
|
||||||
|
<!-- Square -->
|
||||||
|
<div class="aspect-square">1:1</div>
|
||||||
|
|
||||||
|
<!-- Auto (intrinsic) -->
|
||||||
|
<div class="aspect-auto">Natural ratio</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- `aspect-video` - 16:9
|
||||||
|
- `aspect-square` - 1:1
|
||||||
|
- `aspect-auto` - browser default (intrinsic)
|
||||||
|
- `aspect-[4/3]` - arbitrary ratio
|
||||||
|
- Useful for responsive video embeds and image containers
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/aspect-ratio
|
||||||
|
-->
|
||||||
80
.agents/skills/tailwindcss/references/layout-columns.md
Normal file
80
.agents/skills/tailwindcss/references/layout-columns.md
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
---
|
||||||
|
name: layout-columns
|
||||||
|
description: Multi-column layout with columns utility for masonry-like or newspaper layouts
|
||||||
|
---
|
||||||
|
|
||||||
|
# Columns
|
||||||
|
|
||||||
|
Utilities for CSS multi-column layout. Content flows into multiple columns within a single element.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### By number of columns
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="columns-3 gap-8">
|
||||||
|
<img class="aspect-3/2" src="1.jpg" />
|
||||||
|
<img class="aspect-square" src="2.jpg" />
|
||||||
|
<!-- Content flows into 3 columns -->
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### By column width
|
||||||
|
|
||||||
|
Use container scale for ideal column width; column count adjusts automatically:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="columns-3xs gap-4">...</div>
|
||||||
|
<div class="columns-sm gap-6">...</div>
|
||||||
|
<div class="columns-md">...</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Column gap
|
||||||
|
|
||||||
|
Use `gap-*` utilities for space between columns:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="columns-3 gap-8">...</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Responsive
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="columns-2 gap-4 sm:columns-3 sm:gap-8">...</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom value
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="columns-[30vw]">...</div>
|
||||||
|
<div class="columns-(--my-columns)">...</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Column / page breaks
|
||||||
|
|
||||||
|
Use with multi-column or print layouts:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="columns-2">
|
||||||
|
<p>Content...</p>
|
||||||
|
<p class="break-after-column">Force break after this</p>
|
||||||
|
<p>Next column...</p>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
- `break-after-column` / `break-before-column` - column break
|
||||||
|
- `break-after-page` / `break-before-page` - page break (print)
|
||||||
|
- `break-after-avoid` / `break-inside-avoid` - avoid breaking
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- `columns-<n>` - fixed number of columns (e.g. `columns-3`)
|
||||||
|
- `columns-3xs` through `columns-7xl` - column width from container scale
|
||||||
|
- `columns-auto` - auto columns
|
||||||
|
- `gap-*` controls column gap (same as flex/grid gap)
|
||||||
|
- Use for magazine-style layouts, image galleries, long text
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/columns
|
||||||
|
-->
|
||||||
110
.agents/skills/tailwindcss/references/layout-display.md
Normal file
110
.agents/skills/tailwindcss/references/layout-display.md
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
---
|
||||||
|
name: layout-display
|
||||||
|
description: Controlling element display type including flex, grid, block, inline, hidden, and sr-only
|
||||||
|
---
|
||||||
|
|
||||||
|
# Display
|
||||||
|
|
||||||
|
Utilities for controlling the display box type of an element.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Basic display types
|
||||||
|
|
||||||
|
```html
|
||||||
|
<span class="inline">display: inline</span>
|
||||||
|
<span class="inline-block">display: inline-block</span>
|
||||||
|
<div class="block">display: block</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flex and Grid containers
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Flex container -->
|
||||||
|
<div class="flex items-center gap-4">Flex layout</div>
|
||||||
|
<span class="inline-flex items-baseline">Inline flex with text</span>
|
||||||
|
|
||||||
|
<!-- Grid container -->
|
||||||
|
<div class="grid grid-cols-3 gap-4">Grid layout</div>
|
||||||
|
<span class="inline-grid grid-cols-3 gap-4">Inline grid</span>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flow root
|
||||||
|
|
||||||
|
Use `flow-root` to create a block-level element with its own block formatting context (fixes margin collapse):
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="flow-root">
|
||||||
|
<div class="my-4">Content with isolated BFC</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Contents
|
||||||
|
|
||||||
|
Use `contents` for a "phantom" container whose children act like direct children of the parent:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-1">01</div>
|
||||||
|
<div class="contents">
|
||||||
|
<div class="flex-1">02</div>
|
||||||
|
<div class="flex-1">03</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">04</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Table display
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="table w-full">
|
||||||
|
<div class="table-header-group bg-gray-100">
|
||||||
|
<div class="table-row">
|
||||||
|
<div class="table-cell p-2">Header 1</div>
|
||||||
|
<div class="table-cell p-2">Header 2</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="table-row-group">
|
||||||
|
<div class="table-row">
|
||||||
|
<div class="table-cell p-2">Cell 1</div>
|
||||||
|
<div class="table-cell p-2">Cell 2</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hidden
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="hidden">Removed from document flow</div>
|
||||||
|
<div class="md:block hidden">Visible only on md+</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
For visual-only hiding while keeping in DOM, use `invisible` or `opacity-0` instead.
|
||||||
|
|
||||||
|
### Screen reader only
|
||||||
|
|
||||||
|
```html
|
||||||
|
<a href="#">
|
||||||
|
<svg><!-- icon --></svg>
|
||||||
|
<span class="sr-only">Settings</span>
|
||||||
|
</a>
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `not-sr-only` to undo: `<span class="sr-only sm:not-sr-only">Settings</span>`
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- `flex` / `inline-flex` - Flexbox layout
|
||||||
|
- `grid` / `inline-grid` - CSS Grid layout
|
||||||
|
- `block` / `inline` / `inline-block` - Basic flow
|
||||||
|
- `hidden` - `display: none` (removes from flow)
|
||||||
|
- `sr-only` - Visually hidden but accessible to screen readers
|
||||||
|
- `contents` - Children participate in parent's layout
|
||||||
|
- `flow-root` - Establishes new BFC
|
||||||
|
- Table utilities: `table`, `table-row`, `table-cell`, `table-header-group`, etc.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/display
|
||||||
|
-->
|
||||||
110
.agents/skills/tailwindcss/references/layout-flexbox.md
Normal file
110
.agents/skills/tailwindcss/references/layout-flexbox.md
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
---
|
||||||
|
name: layout-flexbox
|
||||||
|
description: Flexbox layout utilities including flex-direction, justify, items, gap, grow, shrink, and wrap
|
||||||
|
---
|
||||||
|
|
||||||
|
# Flexbox
|
||||||
|
|
||||||
|
Utilities for building flex layouts: direction, alignment, gap, and item sizing.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Flex direction and wrap
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="flex flex-row">Horizontal (default)</div>
|
||||||
|
<div class="flex flex-row-reverse">Reversed</div>
|
||||||
|
<div class="flex flex-col">Vertical</div>
|
||||||
|
<div class="flex flex-col-reverse">Vertical reversed</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap">Wrap when needed</div>
|
||||||
|
<div class="flex flex-nowrap">No wrap (default)</div>
|
||||||
|
<div class="flex flex-wrap-reverse">Wrap reversed</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Justify content
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="flex justify-start">Start</div>
|
||||||
|
<div class="flex justify-end">End</div>
|
||||||
|
<div class="flex justify-center">Center</div>
|
||||||
|
<div class="flex justify-between">Space between</div>
|
||||||
|
<div class="flex justify-around">Space around</div>
|
||||||
|
<div class="flex justify-evenly">Space evenly</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Align items
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="flex items-start">Align start</div>
|
||||||
|
<div class="flex items-end">Align end</div>
|
||||||
|
<div class="flex items-center">Center (common)</div>
|
||||||
|
<div class="flex items-baseline">Baseline</div>
|
||||||
|
<div class="flex items-stretch">Stretch (default)</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Align self (on flex children)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="flex">
|
||||||
|
<div class="self-auto">Auto</div>
|
||||||
|
<div class="self-start">Start</div>
|
||||||
|
<div class="self-center">Center</div>
|
||||||
|
<div class="self-stretch">Stretch</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Gap
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="flex gap-4">gap-4 (1rem)</div>
|
||||||
|
<div class="flex gap-x-2 gap-y-4">Different x/y gap</div>
|
||||||
|
<div class="flex gap-0">No gap</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flex grow, shrink, basis
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="flex">
|
||||||
|
<div class="w-14 flex-none">Fixed</div>
|
||||||
|
<div class="flex-1">Grows and shrinks</div>
|
||||||
|
<div class="flex-initial">Shrink only</div>
|
||||||
|
<div class="flex-auto">Grow/shrink with initial size</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
- `flex-1` = `flex: 1 1 0%` - equal distribution
|
||||||
|
- `flex-initial` = `flex: 0 1 auto` - shrink, don't grow
|
||||||
|
- `flex-auto` = `flex: 1 1 auto` - grow/shrink from content size
|
||||||
|
- `flex-none` = `flex: none` - no grow or shrink
|
||||||
|
|
||||||
|
### Order
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="flex">
|
||||||
|
<div class="order-2">Second</div>
|
||||||
|
<div class="order-1">First</div>
|
||||||
|
<div class="order-last">Last</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Use `flex` or `inline-flex` as container (see layout-display)
|
||||||
|
- Direction: `flex-row`, `flex-col`, `flex-row-reverse`, `flex-col-reverse`
|
||||||
|
- Justify: `justify-start`, `justify-center`, `justify-between`, `justify-evenly`
|
||||||
|
- Align: `items-center`, `items-start`, `items-stretch`
|
||||||
|
- Gap: `gap-{n}`, `gap-x-{n}`, `gap-y-{n}` (spacing scale)
|
||||||
|
- Item sizing: `flex-1`, `flex-none`, `flex-auto`, `flex-initial`
|
||||||
|
- Self alignment: `self-center`, `self-start`, etc.
|
||||||
|
- Order: `order-{n}`, `order-first`, `order-last`
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/flex-direction
|
||||||
|
- https://tailwindcss.com/docs/justify-content
|
||||||
|
- https://tailwindcss.com/docs/align-items
|
||||||
|
- https://tailwindcss.com/docs/gap
|
||||||
|
- https://tailwindcss.com/docs/flex
|
||||||
|
- https://tailwindcss.com/docs/order
|
||||||
|
-->
|
||||||
87
.agents/skills/tailwindcss/references/layout-grid.md
Normal file
87
.agents/skills/tailwindcss/references/layout-grid.md
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
---
|
||||||
|
name: layout-grid
|
||||||
|
description: CSS Grid utilities including grid-cols, grid-rows, gap, place-items, and grid placement
|
||||||
|
---
|
||||||
|
|
||||||
|
# Grid
|
||||||
|
|
||||||
|
Utilities for CSS Grid layouts: columns, rows, gap, and item placement.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Grid columns and rows
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="grid grid-cols-3 gap-4">3 equal columns</div>
|
||||||
|
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-4">Responsive</div>
|
||||||
|
<div class="grid grid-cols-none">No explicit tracks</div>
|
||||||
|
|
||||||
|
<div class="grid grid-rows-3 gap-2">3 rows</div>
|
||||||
|
<div class="grid grid-rows-[auto_1fr_auto]">Custom rows</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom grid template
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="grid grid-cols-[200px_minmax(900px,1fr)_100px]">Custom template</div>
|
||||||
|
<div class="grid grid-cols-(--my-cols)">CSS variable</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Subgrid
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="grid grid-cols-4 gap-4">
|
||||||
|
<div>01</div><div>02</div><div>03</div><div>04</div>
|
||||||
|
<div>05</div>
|
||||||
|
<div class="col-span-3 grid grid-cols-subgrid gap-4">
|
||||||
|
<div class="col-start-2">06</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Gap
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="grid gap-4">Uniform gap</div>
|
||||||
|
<div class="grid gap-x-2 gap-y-4">Different x/y</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Place items (align + justify)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="grid place-items-center">Center both</div>
|
||||||
|
<div class="grid place-items-start">Start both</div>
|
||||||
|
<div class="grid items-center justify-center">Equivalent</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Grid placement (col/row span and start)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="grid grid-cols-4 gap-4">
|
||||||
|
<div class="col-span-2">Spans 2 columns</div>
|
||||||
|
<div class="col-start-3">Starts at col 3</div>
|
||||||
|
<div class="row-span-2">Spans 2 rows</div>
|
||||||
|
<div class="col-span-full">Full width</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- `grid` / `inline-grid` - Grid container (see layout-display)
|
||||||
|
- Columns: `grid-cols-{n}`, `grid-cols-none`, `grid-cols-subgrid`, `grid-cols-[...]`
|
||||||
|
- Rows: `grid-rows-{n}`, `grid-rows-[...]`
|
||||||
|
- Gap: `gap-{n}`, `gap-x-{n}`, `gap-y-{n}`
|
||||||
|
- Placement: `col-span-{n}`, `col-start-{n}`, `row-span-{n}`, `row-start-{n}`
|
||||||
|
- `col-span-full` / `row-span-full` - span all
|
||||||
|
- `place-items-*` - shorthand for align + justify
|
||||||
|
- Use `grid-cols-[...]` for custom templates like `minmax()` or `repeat()`
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/grid-template-columns
|
||||||
|
- https://tailwindcss.com/docs/grid-template-rows
|
||||||
|
- https://tailwindcss.com/docs/gap
|
||||||
|
- https://tailwindcss.com/docs/place-items
|
||||||
|
- https://tailwindcss.com/docs/grid-column
|
||||||
|
- https://tailwindcss.com/docs/grid-row
|
||||||
|
-->
|
||||||
97
.agents/skills/tailwindcss/references/layout-height.md
Normal file
97
.agents/skills/tailwindcss/references/layout-height.md
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
---
|
||||||
|
name: layout-height
|
||||||
|
description: Setting element height with spacing scale, fractions, viewport units, and content-based sizing
|
||||||
|
---
|
||||||
|
|
||||||
|
# Height
|
||||||
|
|
||||||
|
Utilities for setting the height of an element.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Spacing scale
|
||||||
|
|
||||||
|
Use `h-<number>` utilities based on the spacing scale:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="h-24">h-24</div>
|
||||||
|
<div class="h-48">h-48</div>
|
||||||
|
<div class="h-64">h-64</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Percentage heights
|
||||||
|
|
||||||
|
Use `h-full` or `h-<fraction>` for percentage-based heights:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="h-full">100%</div>
|
||||||
|
<div class="h-1/2">50%</div>
|
||||||
|
<div class="h-3/4">75%</div>
|
||||||
|
<div class="h-9/10">90%</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Viewport units
|
||||||
|
|
||||||
|
Use `h-screen` for full viewport height, or dynamic viewport units:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="h-screen">Full viewport height (100vh)</div>
|
||||||
|
<div class="h-dvh">Dynamic viewport height</div>
|
||||||
|
<div class="h-svh">Small viewport height</div>
|
||||||
|
<div class="h-lvh">Large viewport height</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Content-based heights
|
||||||
|
|
||||||
|
Use `h-auto`, `h-fit`, `h-min`, `h-max` for content-based sizing:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="h-auto">Auto height</div>
|
||||||
|
<div class="h-fit">Fit content</div>
|
||||||
|
<div class="h-min">Min content</div>
|
||||||
|
<div class="h-max">Max content</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Line height
|
||||||
|
|
||||||
|
Use `h-lh` to match line height:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="h-lh">Matches line height</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Size utility
|
||||||
|
|
||||||
|
Use `size-<number>` to set both width and height:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="size-16">16x16</div>
|
||||||
|
<div class="size-24">24x24</div>
|
||||||
|
<div class="size-full">100% x 100%</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom values
|
||||||
|
|
||||||
|
Use arbitrary values for custom heights:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="h-[117px]">Custom pixel height</div>
|
||||||
|
<div class="h-[50vh]">Custom viewport height</div>
|
||||||
|
<div class="h-[calc(100vh-2rem)]">Custom calculation</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Spacing scale: `h-0` through `h-96` (and beyond)
|
||||||
|
- Fractions: `h-1/2`, `h-1/3`, `h-2/3`, `h-1/4`, `h-3/4`, `h-9/10`
|
||||||
|
- Viewport units: `h-screen` (100vh), `h-dvh`, `h-svh`, `h-lvh`
|
||||||
|
- `h-dvh` adapts to browser UI (address bar, etc.)
|
||||||
|
- `h-svh` uses smallest viewport height
|
||||||
|
- `h-lvh` uses largest viewport height
|
||||||
|
- `size-*` utilities set both width and height simultaneously
|
||||||
|
- Use `h-auto` to reset height at specific breakpoints
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/height
|
||||||
|
-->
|
||||||
91
.agents/skills/tailwindcss/references/layout-inset.md
Normal file
91
.agents/skills/tailwindcss/references/layout-inset.md
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
---
|
||||||
|
name: layout-inset
|
||||||
|
description: Controlling placement of positioned elements with top, right, bottom, left, and inset utilities
|
||||||
|
---
|
||||||
|
|
||||||
|
# Top / Right / Bottom / Left
|
||||||
|
|
||||||
|
Utilities for controlling the placement of positioned elements.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Basic positioning
|
||||||
|
|
||||||
|
Use `top-<number>`, `right-<number>`, `bottom-<number>`, `left-<number>` to position elements:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Pin to top left corner -->
|
||||||
|
<div class="relative size-32">
|
||||||
|
<div class="absolute top-0 left-0 size-16">01</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Span top edge -->
|
||||||
|
<div class="relative size-32">
|
||||||
|
<div class="absolute inset-x-0 top-0 h-16">02</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Fill entire parent -->
|
||||||
|
<div class="relative size-32">
|
||||||
|
<div class="absolute inset-0">05</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Inset utilities
|
||||||
|
|
||||||
|
Use `inset-<number>` for all sides, `inset-x-<number>` for horizontal, `inset-y-<number>` for vertical:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="absolute inset-0">Fill parent</div>
|
||||||
|
<div class="absolute inset-x-0 top-0">Span top</div>
|
||||||
|
<div class="absolute inset-y-0 left-0">Span left</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Negative values
|
||||||
|
|
||||||
|
Prefix with a dash for negative values:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="relative size-32">
|
||||||
|
<div class="absolute -top-4 -left-4 size-14"></div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logical properties
|
||||||
|
|
||||||
|
Use `start-<number>` and `end-<number>` for RTL-aware positioning:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div dir="ltr">
|
||||||
|
<div class="absolute start-0 top-0">Left in LTR</div>
|
||||||
|
</div>
|
||||||
|
<div dir="rtl">
|
||||||
|
<div class="absolute start-0 top-0">Right in RTL</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Percentage and custom values
|
||||||
|
|
||||||
|
Use fractions for percentages or arbitrary values:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
|
||||||
|
Centered
|
||||||
|
</div>
|
||||||
|
<div class="absolute top-[117px] left-[20%]">
|
||||||
|
Custom position
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- `inset-0` sets all sides to 0 (equivalent to `top-0 right-0 bottom-0 left-0`)
|
||||||
|
- `inset-x-0` sets left and right to 0
|
||||||
|
- `inset-y-0` sets top and bottom to 0
|
||||||
|
- Use `start`/`end` for logical properties that adapt to text direction
|
||||||
|
- Negative values use dash prefix: `-top-4`, `-left-8`
|
||||||
|
- Combine with `position` utilities (`absolute`, `fixed`, `relative`, `sticky`)
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/top-right-bottom-left
|
||||||
|
-->
|
||||||
126
.agents/skills/tailwindcss/references/layout-margin.md
Normal file
126
.agents/skills/tailwindcss/references/layout-margin.md
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
---
|
||||||
|
name: layout-margin
|
||||||
|
description: Controlling element margins with spacing scale, negative values, logical properties, and space utilities
|
||||||
|
---
|
||||||
|
|
||||||
|
# Margin
|
||||||
|
|
||||||
|
Utilities for controlling an element's margin.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### All sides
|
||||||
|
|
||||||
|
Use `m-<number>` to set margin on all sides:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="m-4">Margin on all sides</div>
|
||||||
|
<div class="m-8">Larger margin</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Individual sides
|
||||||
|
|
||||||
|
Use `mt-<number>`, `mr-<number>`, `mb-<number>`, `ml-<number>`:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="mt-6">Top margin</div>
|
||||||
|
<div class="mr-4">Right margin</div>
|
||||||
|
<div class="mb-8">Bottom margin</div>
|
||||||
|
<div class="ml-2">Left margin</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Horizontal and vertical
|
||||||
|
|
||||||
|
Use `mx-<number>` for horizontal, `my-<number>` for vertical:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="mx-8">Horizontal margin</div>
|
||||||
|
<div class="my-8">Vertical margin</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Negative margins
|
||||||
|
|
||||||
|
Prefix with dash for negative values:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="-mt-8">Negative top margin</div>
|
||||||
|
<div class="-mx-4">Negative horizontal margin</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Auto margins
|
||||||
|
|
||||||
|
Use `m-auto` or directional auto margins for centering:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="mx-auto">Centered horizontally</div>
|
||||||
|
<div class="ml-auto">Pushed to right</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logical properties
|
||||||
|
|
||||||
|
Use `ms-<number>` (margin-inline-start) and `me-<number>` (margin-inline-end) for RTL support:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div dir="ltr">
|
||||||
|
<div class="ms-8">Left margin in LTR</div>
|
||||||
|
<div class="me-8">Right margin in LTR</div>
|
||||||
|
</div>
|
||||||
|
<div dir="rtl">
|
||||||
|
<div class="ms-8">Right margin in RTL</div>
|
||||||
|
<div class="me-8">Left margin in RTL</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Space between children
|
||||||
|
|
||||||
|
Use `space-x-<number>` or `space-y-<number>` to add margin between children:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="flex space-x-4">
|
||||||
|
<div>01</div>
|
||||||
|
<div>02</div>
|
||||||
|
<div>03</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col space-y-8">
|
||||||
|
<div>01</div>
|
||||||
|
<div>02</div>
|
||||||
|
<div>03</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reversing space
|
||||||
|
|
||||||
|
Use `space-x-reverse` or `space-y-reverse` with reversed flex directions:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="flex flex-row-reverse space-x-4 space-x-reverse">
|
||||||
|
<div>01</div>
|
||||||
|
<div>02</div>
|
||||||
|
<div>03</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom values
|
||||||
|
|
||||||
|
Use arbitrary values for custom margins:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="m-[5px]">Custom margin</div>
|
||||||
|
<div class="mx-[calc(50%-1rem)]">Custom calculation</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Spacing scale: `m-0` through `m-96` (and beyond)
|
||||||
|
- Negative: prefix with dash (`-m-4`, `-mt-8`, `-mx-4`)
|
||||||
|
- Auto: `m-auto`, `mx-auto`, `my-auto`, `mt-auto`, etc.
|
||||||
|
- Logical: `ms-*` (start), `me-*` (end) adapt to text direction
|
||||||
|
- Space utilities: `space-x-*`, `space-y-*` add margin to all children except last
|
||||||
|
- Space reverse: `space-x-reverse`, `space-y-reverse` for reversed flex layouts
|
||||||
|
- Limitations: Space utilities don't work well with grids or complex layouts - use `gap` utilities instead
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/margin
|
||||||
|
-->
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
name: layout-min-max-sizing
|
||||||
|
description: Setting minimum and maximum width and height with min-w, max-w, min-h, max-h
|
||||||
|
---
|
||||||
|
|
||||||
|
# Min & Max Sizing
|
||||||
|
|
||||||
|
Utilities for constraining element dimensions with minimum and maximum width/height.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Min width
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Spacing scale -->
|
||||||
|
<div class="min-w-24">min-w-24</div>
|
||||||
|
<div class="min-w-64">min-w-64</div>
|
||||||
|
|
||||||
|
<!-- Percentage -->
|
||||||
|
<div class="min-w-full">min-w-full</div>
|
||||||
|
<div class="min-w-3/4">min-w-3/4</div>
|
||||||
|
|
||||||
|
<!-- Container scale -->
|
||||||
|
<div class="min-w-sm">min-w-sm</div>
|
||||||
|
<div class="min-w-xl">min-w-xl</div>
|
||||||
|
|
||||||
|
<!-- Content-based -->
|
||||||
|
<div class="min-w-min">min-content</div>
|
||||||
|
<div class="min-w-max">max-content</div>
|
||||||
|
<div class="min-w-fit">fit-content</div>
|
||||||
|
<div class="min-w-auto">auto</div>
|
||||||
|
|
||||||
|
<!-- Custom -->
|
||||||
|
<div class="min-w-[220px]">Custom</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Max width
|
||||||
|
|
||||||
|
Use `max-w-<value>` with similar scales: spacing numbers, fractions (`max-w-1/2`), container sizes (`max-w-md`), `max-w-full`, `max-w-screen`, `max-w-min`, `max-w-max`, `max-w-fit`, `max-w-none`.
|
||||||
|
|
||||||
|
### Min / Max height
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="min-h-screen">At least full viewport height</div>
|
||||||
|
<div class="min-h-0">Allow shrinking in flex</div>
|
||||||
|
<div class="max-h-96 overflow-y-auto">Scrollable with max height</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- min-w: spacing scale, fractions, container scale (3xs–7xl), `full`, `screen`, `min`, `max`, `fit`, `auto`
|
||||||
|
- max-w: same options plus `none`
|
||||||
|
- min-h / max-h: similar scales; `min-h-0` important for flex children to shrink
|
||||||
|
- Viewport units: `min-w-screen`, `min-w-dvw`, `min-w-svw`, `min-w-lvw`
|
||||||
|
- Container scale: `min-w-3xs` through `min-w-7xl` map to `--container-*` variables
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/min-width
|
||||||
|
- https://tailwindcss.com/docs/max-width
|
||||||
|
- https://tailwindcss.com/docs/min-height
|
||||||
|
- https://tailwindcss.com/docs/max-height
|
||||||
|
-->
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
---
|
||||||
|
name: layout-object-fit-position
|
||||||
|
description: Controlling how replaced elements (images, video) are resized and positioned within their container
|
||||||
|
---
|
||||||
|
|
||||||
|
# Object Fit & Object Position
|
||||||
|
|
||||||
|
Utilities for controlling how replaced elements like `<img>` and `<video>` are resized and positioned within their container.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Object fit
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Cover container, may crop -->
|
||||||
|
<img class="h-48 w-96 object-cover" src="photo.jpg" />
|
||||||
|
|
||||||
|
<!-- Contain within, may letterbox -->
|
||||||
|
<img class="h-48 w-96 object-contain" src="photo.jpg" />
|
||||||
|
|
||||||
|
<!-- Stretch to fill -->
|
||||||
|
<img class="h-48 w-96 object-fill" src="photo.jpg" />
|
||||||
|
|
||||||
|
<!-- Scale down only if needed, else original size -->
|
||||||
|
<img class="h-48 w-96 object-scale-down" src="photo.jpg" />
|
||||||
|
|
||||||
|
<!-- Original size, ignore container -->
|
||||||
|
<img class="h-48 w-96 object-none" src="photo.jpg" />
|
||||||
|
```
|
||||||
|
|
||||||
|
### Object position
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Named positions -->
|
||||||
|
<img class="size-24 object-cover object-top-left" src="photo.jpg" />
|
||||||
|
<img class="size-24 object-cover object-center" src="photo.jpg" />
|
||||||
|
<img class="size-24 object-cover object-bottom-right" src="photo.jpg" />
|
||||||
|
|
||||||
|
<!-- Custom value -->
|
||||||
|
<img class="object-cover object-[25%_75%]" src="photo.jpg" />
|
||||||
|
<img class="object-(--my-object)" src="photo.jpg" />
|
||||||
|
```
|
||||||
|
|
||||||
|
### Responsive
|
||||||
|
|
||||||
|
```html
|
||||||
|
<img class="object-contain md:object-cover object-center md:object-top" src="photo.jpg" />
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- `object-contain` - maintain aspect ratio, fit inside container
|
||||||
|
- `object-cover` - maintain aspect ratio, cover container (may crop)
|
||||||
|
- `object-fill` - stretch to fill container
|
||||||
|
- `object-scale-down` - like `contain` but never upscale
|
||||||
|
- `object-none` - original size, ignore container
|
||||||
|
- Position: `object-top-left`, `object-top`, `object-top-right`, `object-left`, `object-center`, `object-right`, `object-bottom-left`, `object-bottom`, `object-bottom-right`
|
||||||
|
- Custom: `object-[25%_75%]`, `object-(--custom-property)`
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/object-fit
|
||||||
|
- https://tailwindcss.com/docs/object-position
|
||||||
|
-->
|
||||||
57
.agents/skills/tailwindcss/references/layout-overflow.md
Normal file
57
.agents/skills/tailwindcss/references/layout-overflow.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
---
|
||||||
|
name: layout-overflow
|
||||||
|
description: Controlling how elements handle content that overflows their container
|
||||||
|
---
|
||||||
|
|
||||||
|
# Overflow
|
||||||
|
|
||||||
|
Utilities for controlling how an element handles content that is too large for the container.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Basic overflow
|
||||||
|
|
||||||
|
Use `overflow-auto`, `overflow-hidden`, `overflow-visible`, `overflow-scroll`:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="overflow-auto">Scrolls if needed</div>
|
||||||
|
<div class="overflow-hidden">Clips overflow</div>
|
||||||
|
<div class="overflow-visible">Shows overflow</div>
|
||||||
|
<div class="overflow-scroll">Always shows scrollbars</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Axis-specific overflow
|
||||||
|
|
||||||
|
Use `overflow-x-*` or `overflow-y-*` for horizontal/vertical control:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="overflow-x-auto overflow-y-hidden">
|
||||||
|
Horizontal scroll, vertical clip
|
||||||
|
</div>
|
||||||
|
<div class="overflow-x-scroll overflow-y-auto">
|
||||||
|
Horizontal always scrolls, vertical scrolls if needed
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Overflow clip
|
||||||
|
|
||||||
|
Use `overflow-clip` for clip behavior (similar to hidden but different scroll behavior):
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="overflow-clip">Clips without creating scroll container</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- `overflow-auto` - shows scrollbars only when needed
|
||||||
|
- `overflow-hidden` - clips content that overflows
|
||||||
|
- `overflow-visible` - allows content to overflow (default for most elements)
|
||||||
|
- `overflow-scroll` - always shows scrollbars
|
||||||
|
- `overflow-clip` - clips without creating scroll container
|
||||||
|
- Use `overflow-x-*` and `overflow-y-*` for axis-specific control
|
||||||
|
- Common pattern: `overflow-hidden` for images, `overflow-auto` for scrollable content
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/overflow
|
||||||
|
-->
|
||||||
77
.agents/skills/tailwindcss/references/layout-padding.md
Normal file
77
.agents/skills/tailwindcss/references/layout-padding.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
---
|
||||||
|
name: layout-padding
|
||||||
|
description: Controlling element padding with spacing scale, logical properties, and directional utilities
|
||||||
|
---
|
||||||
|
|
||||||
|
# Padding
|
||||||
|
|
||||||
|
Utilities for controlling an element's padding.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### All sides
|
||||||
|
|
||||||
|
Use `p-<number>` to set padding on all sides:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="p-4">Padding on all sides</div>
|
||||||
|
<div class="p-8">Larger padding</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Individual sides
|
||||||
|
|
||||||
|
Use `pt-<number>`, `pr-<number>`, `pb-<number>`, `pl-<number>`:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="pt-6">Top padding</div>
|
||||||
|
<div class="pr-4">Right padding</div>
|
||||||
|
<div class="pb-8">Bottom padding</div>
|
||||||
|
<div class="pl-2">Left padding</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Horizontal and vertical
|
||||||
|
|
||||||
|
Use `px-<number>` for horizontal, `py-<number>` for vertical:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="px-8">Horizontal padding</div>
|
||||||
|
<div class="py-8">Vertical padding</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logical properties
|
||||||
|
|
||||||
|
Use `ps-<number>` (padding-inline-start) and `pe-<number>` (padding-inline-end) for RTL support:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div dir="ltr">
|
||||||
|
<div class="ps-8">Left padding in LTR</div>
|
||||||
|
<div class="pe-8">Right padding in LTR</div>
|
||||||
|
</div>
|
||||||
|
<div dir="rtl">
|
||||||
|
<div class="ps-8">Right padding in RTL</div>
|
||||||
|
<div class="pe-8">Left padding in RTL</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom values
|
||||||
|
|
||||||
|
Use arbitrary values for custom padding:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="p-[5px]">Custom padding</div>
|
||||||
|
<div class="px-[calc(50%-1rem)]">Custom calculation</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Spacing scale: `p-0` through `p-96` (and beyond)
|
||||||
|
- Individual: `pt-*`, `pr-*`, `pb-*`, `pl-*` for specific sides
|
||||||
|
- Axes: `px-*` (horizontal), `py-*` (vertical)
|
||||||
|
- Logical: `ps-*` (start), `pe-*` (end) adapt to text direction
|
||||||
|
- No negative padding - padding cannot be negative in CSS
|
||||||
|
- Common patterns: `p-4`, `px-6`, `py-8`, `pt-2`, `pb-4`
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/padding
|
||||||
|
-->
|
||||||
85
.agents/skills/tailwindcss/references/layout-position.md
Normal file
85
.agents/skills/tailwindcss/references/layout-position.md
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
---
|
||||||
|
name: layout-position
|
||||||
|
description: Controlling element positioning with static, relative, absolute, fixed, and sticky utilities
|
||||||
|
---
|
||||||
|
|
||||||
|
# Position
|
||||||
|
|
||||||
|
Utilities for controlling how an element is positioned in the document.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Static positioning
|
||||||
|
|
||||||
|
Use `static` to position an element according to the normal flow. Offsets are ignored and it won't act as a position reference for absolutely positioned children:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="static">
|
||||||
|
<p>Static parent</p>
|
||||||
|
<div class="absolute bottom-0 left-0">
|
||||||
|
<p>Absolute child</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Relative positioning
|
||||||
|
|
||||||
|
Use `relative` to position an element in normal flow. Offsets are calculated relative to the element's normal position, and it acts as a position reference for absolutely positioned children:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="relative">
|
||||||
|
<p>Relative parent</p>
|
||||||
|
<div class="absolute bottom-0 left-0">
|
||||||
|
<p>Absolute child</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Absolute positioning
|
||||||
|
|
||||||
|
Use `absolute` to position an element outside the normal flow. Neighboring elements act as if it doesn't exist. Offsets are calculated relative to the nearest positioned parent:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="relative">
|
||||||
|
<div class="absolute top-0 right-0">
|
||||||
|
<p>Absolute child</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fixed positioning
|
||||||
|
|
||||||
|
Use `fixed` to position an element relative to the browser window. Offsets are calculated relative to the viewport:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="fixed top-0 right-0 left-0">
|
||||||
|
<p>Fixed header</p>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sticky positioning
|
||||||
|
|
||||||
|
Use `sticky` to position an element as `relative` until it crosses a threshold, then treat it as `fixed` until its parent is off screen:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div>
|
||||||
|
<div class="sticky top-0">
|
||||||
|
<p>Sticky header</p>
|
||||||
|
</div>
|
||||||
|
<div>Content...</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- `static` is the default - elements flow normally
|
||||||
|
- `relative` maintains normal flow but allows offsets and becomes a positioning context
|
||||||
|
- `absolute` removes from flow and positions relative to nearest positioned ancestor
|
||||||
|
- `fixed` positions relative to viewport, stays in place when scrolling
|
||||||
|
- `sticky` combines relative and fixed behavior based on scroll position
|
||||||
|
- Always use with offset utilities like `top-0`, `right-0`, `inset-0`, etc.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/position
|
||||||
|
-->
|
||||||
67
.agents/skills/tailwindcss/references/layout-tables.md
Normal file
67
.agents/skills/tailwindcss/references/layout-tables.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
---
|
||||||
|
name: layout-tables
|
||||||
|
description: Table layout and border behavior with border-collapse, table-layout
|
||||||
|
---
|
||||||
|
|
||||||
|
# Table Layout
|
||||||
|
|
||||||
|
Utilities for controlling table display, border behavior, and layout algorithm.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Border collapse
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Collapsed: adjacent borders merge into single border -->
|
||||||
|
<table class="border-collapse border border-gray-400">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="border border-gray-300 p-2">State</th>
|
||||||
|
<th class="border border-gray-300 p-2">City</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="border border-gray-300 p-2">Indiana</td>
|
||||||
|
<td class="border border-gray-300 p-2">Indianapolis</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- Separate: each cell has its own borders -->
|
||||||
|
<table class="border-separate border border-gray-400">...</table>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Table layout
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Auto: columns sized to content -->
|
||||||
|
<table class="table-auto">...</table>
|
||||||
|
|
||||||
|
<!-- Fixed: columns use first row widths -->
|
||||||
|
<table class="table-fixed">...</table>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Table display (from layout-display)
|
||||||
|
|
||||||
|
Combine with `table`, `table-row`, `table-cell`, `table-header-group`, etc. for semantic table structure.
|
||||||
|
|
||||||
|
### Responsive
|
||||||
|
|
||||||
|
```html
|
||||||
|
<table class="border-collapse md:border-separate">...</table>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- `border-collapse` - adjacent borders merge (single border between cells)
|
||||||
|
- `border-separate` - each cell displays its own borders
|
||||||
|
- `table-auto` - column widths from content
|
||||||
|
- `table-fixed` - fixed layout; first row sets column widths
|
||||||
|
- In v4, `border-*` and `divide-*` default to `currentColor`; specify a color (e.g. `border-gray-200`) explicitly
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/border-collapse
|
||||||
|
- https://tailwindcss.com/docs/table-layout
|
||||||
|
-->
|
||||||
102
.agents/skills/tailwindcss/references/layout-width.md
Normal file
102
.agents/skills/tailwindcss/references/layout-width.md
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
---
|
||||||
|
name: layout-width
|
||||||
|
description: Setting element width with spacing scale, fractions, container sizes, and viewport units
|
||||||
|
---
|
||||||
|
|
||||||
|
# Width
|
||||||
|
|
||||||
|
Utilities for setting the width of an element.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Spacing scale
|
||||||
|
|
||||||
|
Use `w-<number>` utilities based on the spacing scale:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="w-24">w-24</div>
|
||||||
|
<div class="w-48">w-48</div>
|
||||||
|
<div class="w-64">w-64</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Percentage widths
|
||||||
|
|
||||||
|
Use `w-full` or `w-<fraction>` for percentage-based widths:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="flex">
|
||||||
|
<div class="w-1/2">50%</div>
|
||||||
|
<div class="w-1/2">50%</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex">
|
||||||
|
<div class="w-2/5">40%</div>
|
||||||
|
<div class="w-3/5">60%</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full">100%</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Container scale
|
||||||
|
|
||||||
|
Use container size utilities like `w-sm`, `w-md`, `w-lg`:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="w-sm">Small container</div>
|
||||||
|
<div class="w-md">Medium container</div>
|
||||||
|
<div class="w-xl">Extra large container</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Viewport units
|
||||||
|
|
||||||
|
Use `w-screen` for full viewport width, or dynamic viewport units:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="w-screen">Full viewport width</div>
|
||||||
|
<div class="w-dvw">Dynamic viewport width</div>
|
||||||
|
<div class="w-svw">Small viewport width</div>
|
||||||
|
<div class="w-lvw">Large viewport width</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Content-based widths
|
||||||
|
|
||||||
|
Use `w-auto`, `w-fit`, `w-min`, `w-max` for content-based sizing:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="w-auto">Auto width</div>
|
||||||
|
<div class="w-fit">Fit content</div>
|
||||||
|
<div class="w-min">Min content</div>
|
||||||
|
<div class="w-max">Max content</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Size utility
|
||||||
|
|
||||||
|
Use `size-<number>` to set both width and height:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="size-16">16x16</div>
|
||||||
|
<div class="size-24">24x24</div>
|
||||||
|
<div class="size-full">100% x 100%</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom values
|
||||||
|
|
||||||
|
Use arbitrary values for custom widths:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="w-[117px]">Custom pixel width</div>
|
||||||
|
<div class="w-[50%]">Custom percentage</div>
|
||||||
|
<div class="w-[calc(100%-2rem)]">Custom calculation</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Spacing scale: `w-0` through `w-96` (and beyond)
|
||||||
|
- Fractions: `w-1/2`, `w-1/3`, `w-2/3`, `w-1/4`, `w-3/4`, `w-1/5`, `w-4/5`, `w-1/6`, `w-5/6`
|
||||||
|
- Container sizes: `w-3xs`, `w-2xs`, `w-xs`, `w-sm`, `w-md`, `w-lg`, `w-xl`, `w-2xl`, `w-3xl`, `w-4xl`, `w-5xl`, `w-6xl`, `w-7xl`
|
||||||
|
- Viewport units: `w-screen` (100vw), `w-dvw`, `w-svw`, `w-lvw`
|
||||||
|
- `size-*` utilities set both width and height simultaneously
|
||||||
|
- Use `w-auto` to reset width at specific breakpoints
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/width
|
||||||
|
-->
|
||||||
68
.agents/skills/tailwindcss/references/transform-base.md
Normal file
68
.agents/skills/tailwindcss/references/transform-base.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
---
|
||||||
|
name: transform-base
|
||||||
|
description: Base transform utilities for enabling transforms, hardware acceleration, and custom transform values
|
||||||
|
---
|
||||||
|
|
||||||
|
# Transform
|
||||||
|
|
||||||
|
Base utilities for transforming elements.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Hardware acceleration
|
||||||
|
|
||||||
|
Use `transform-gpu` to force GPU acceleration for better performance:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="scale-150 transform-gpu">
|
||||||
|
GPU-accelerated transform
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### CPU rendering
|
||||||
|
|
||||||
|
Use `transform-cpu` to force CPU rendering if needed:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="scale-150 transform-cpu">
|
||||||
|
CPU-rendered transform
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Removing transforms
|
||||||
|
|
||||||
|
Use `transform-none` to remove all transforms:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="skew-y-3 md:transform-none">
|
||||||
|
Skewed on mobile, normal on desktop
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom transforms
|
||||||
|
|
||||||
|
Use arbitrary values for custom transform functions:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="transform-[matrix(1,2,3,4,5,6)]">
|
||||||
|
Custom matrix transform
|
||||||
|
</div>
|
||||||
|
<div class="transform-[perspective(1000px)_rotateX(45deg)]">
|
||||||
|
Custom 3D transform
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- `transform-gpu` enables hardware acceleration with `translateZ(0)`
|
||||||
|
- `transform-cpu` forces CPU rendering (removes GPU acceleration)
|
||||||
|
- `transform-none` removes all transforms at once
|
||||||
|
- Use `transform-gpu` for better animation performance
|
||||||
|
- Custom transforms use arbitrary values: `transform-[...]`
|
||||||
|
- Transform utilities (translate, rotate, scale, skew) automatically enable transform
|
||||||
|
- Hardware acceleration is usually beneficial for animations
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/transform
|
||||||
|
-->
|
||||||
70
.agents/skills/tailwindcss/references/transform-rotate.md
Normal file
70
.agents/skills/tailwindcss/references/transform-rotate.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
---
|
||||||
|
name: transform-rotate
|
||||||
|
description: Rotating elements in 2D and 3D space with degree values and custom rotations
|
||||||
|
---
|
||||||
|
|
||||||
|
# Rotate
|
||||||
|
|
||||||
|
Utilities for rotating elements.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### 2D rotation
|
||||||
|
|
||||||
|
Use `rotate-<number>` to rotate by degrees:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="rotate-45">45 degrees</div>
|
||||||
|
<div class="rotate-90">90 degrees</div>
|
||||||
|
<div class="-rotate-45">-45 degrees (counterclockwise)</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3D rotation
|
||||||
|
|
||||||
|
Use `rotate-x-<number>`, `rotate-y-<number>`, `rotate-z-<number>` for 3D rotation:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="rotate-x-50 rotate-z-45">3D rotation</div>
|
||||||
|
<div class="rotate-y-25 rotate-z-30">Combined 3D rotation</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common rotations
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="rotate-90">Quarter turn</div>
|
||||||
|
<div class="rotate-180">Half turn</div>
|
||||||
|
<div class="rotate-270">Three-quarter turn</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom values
|
||||||
|
|
||||||
|
Use arbitrary values for custom rotations:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="rotate-[3.142rad]">Custom radian rotation</div>
|
||||||
|
<div class="rotate-[45deg]">Explicit degree rotation</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Removing rotation
|
||||||
|
|
||||||
|
Use `rotate-none` to remove rotation:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="rotate-45 md:rotate-none">Rotated on mobile only</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- `rotate-*` rotates in 2D plane (around z-axis)
|
||||||
|
- `rotate-x-*` rotates around x-axis (3D)
|
||||||
|
- `rotate-y-*` rotates around y-axis (3D)
|
||||||
|
- `rotate-z-*` rotates around z-axis (3D, same as `rotate-*`)
|
||||||
|
- Negative values rotate counterclockwise: `-rotate-45`
|
||||||
|
- Common values: `45`, `90`, `180`, `270` degrees
|
||||||
|
- Can combine multiple axes: `rotate-x-50 rotate-y-30 rotate-z-45`
|
||||||
|
- Use `rotate-none` to remove all rotations
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/rotate
|
||||||
|
-->
|
||||||
83
.agents/skills/tailwindcss/references/transform-scale.md
Normal file
83
.agents/skills/tailwindcss/references/transform-scale.md
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
---
|
||||||
|
name: transform-scale
|
||||||
|
description: Scaling elements uniformly or on specific axes with percentage values
|
||||||
|
---
|
||||||
|
|
||||||
|
# Scale
|
||||||
|
|
||||||
|
Utilities for scaling elements.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Uniform scaling
|
||||||
|
|
||||||
|
Use `scale-<number>` to scale on both axes (number represents percentage):
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="scale-75">75% size</div>
|
||||||
|
<div class="scale-100">100% size (default)</div>
|
||||||
|
<div class="scale-125">125% size</div>
|
||||||
|
<div class="scale-150">150% size</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Axis-specific scaling
|
||||||
|
|
||||||
|
Use `scale-x-<number>` or `scale-y-<number>` to scale on one axis:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="scale-x-75">75% width</div>
|
||||||
|
<div class="scale-y-125">125% height</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Negative scaling
|
||||||
|
|
||||||
|
Use negative values to mirror and scale:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="-scale-x-100">Mirrored horizontally</div>
|
||||||
|
<div class="-scale-y-100">Mirrored vertically</div>
|
||||||
|
<div class="-scale-100">Mirrored both axes</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hover effects
|
||||||
|
|
||||||
|
Common pattern for interactive scaling:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="scale-95 hover:scale-100 transition-transform">
|
||||||
|
Grows on hover
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Removing scale
|
||||||
|
|
||||||
|
Use `scale-none` to remove scaling:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="scale-125 md:scale-none">Scaled on mobile only</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom values
|
||||||
|
|
||||||
|
Use arbitrary values for custom scaling:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="scale-[1.7]">Custom scale value</div>
|
||||||
|
<div class="scale-x-[0.8]">Custom x-axis scale</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- `scale-*` scales uniformly on both axes
|
||||||
|
- `scale-x-*` scales horizontally only
|
||||||
|
- `scale-y-*` scales vertically only
|
||||||
|
- Values represent percentages: `scale-75` = 75%, `scale-125` = 125%
|
||||||
|
- `scale-100` is the default (no scaling)
|
||||||
|
- Negative values mirror the element: `-scale-x-100` flips horizontally
|
||||||
|
- Common for hover effects: `hover:scale-110`, `active:scale-95`
|
||||||
|
- Use `scale-none` to remove all scaling
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/scale
|
||||||
|
-->
|
||||||
62
.agents/skills/tailwindcss/references/transform-skew.md
Normal file
62
.agents/skills/tailwindcss/references/transform-skew.md
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
---
|
||||||
|
name: transform-skew
|
||||||
|
description: Skewing elements on x and y axes with degree values
|
||||||
|
---
|
||||||
|
|
||||||
|
# Skew
|
||||||
|
|
||||||
|
Utilities for skewing (distorting) elements.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Skewing both axes
|
||||||
|
|
||||||
|
Use `skew-<number>` to skew on both axes:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="skew-3">Slight skew</div>
|
||||||
|
<div class="skew-6">Moderate skew</div>
|
||||||
|
<div class="skew-12">Strong skew</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Axis-specific skewing
|
||||||
|
|
||||||
|
Use `skew-x-<number>` or `skew-y-<number>` to skew on one axis:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="skew-x-12">Skewed horizontally</div>
|
||||||
|
<div class="skew-y-6">Skewed vertically</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Negative skewing
|
||||||
|
|
||||||
|
Use negative values for opposite direction:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="-skew-3">Negative skew</div>
|
||||||
|
<div class="-skew-x-12">Negative x-axis skew</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom values
|
||||||
|
|
||||||
|
Use arbitrary values for custom skew:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="skew-[3.142rad]">Custom radian skew</div>
|
||||||
|
<div class="skew-x-[15deg]">Custom degree skew</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- `skew-*` skews on both x and y axes
|
||||||
|
- `skew-x-*` skews horizontally only
|
||||||
|
- `skew-y-*` skews vertically only
|
||||||
|
- Values are in degrees: `skew-3` = 3 degrees
|
||||||
|
- Negative values skew in opposite direction
|
||||||
|
- Common values: `3`, `6`, `12` degrees for subtle effects
|
||||||
|
- Use sparingly - excessive skewing can make text hard to read
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/skew
|
||||||
|
-->
|
||||||
77
.agents/skills/tailwindcss/references/transform-translate.md
Normal file
77
.agents/skills/tailwindcss/references/transform-translate.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
---
|
||||||
|
name: transform-translate
|
||||||
|
description: Translating elements on x, y, and z axes with spacing scale, percentages, and custom values
|
||||||
|
---
|
||||||
|
|
||||||
|
# Translate
|
||||||
|
|
||||||
|
Utilities for translating (moving) elements.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Spacing scale
|
||||||
|
|
||||||
|
Use `translate-<number>` to translate on both axes, or `translate-x-<number>` / `translate-y-<number>` for single axis:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="translate-2">Moved 2 units</div>
|
||||||
|
<div class="-translate-4">Moved -4 units</div>
|
||||||
|
<div class="translate-x-4">Moved right 4 units</div>
|
||||||
|
<div class="translate-y-6">Moved down 6 units</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Percentage translation
|
||||||
|
|
||||||
|
Use `translate-<fraction>` to translate by percentage of element size:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="translate-1/2">Moved 50% on both axes</div>
|
||||||
|
<div class="translate-x-1/4">Moved 25% right</div>
|
||||||
|
<div class="-translate-y-full">Moved 100% up</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Centering elements
|
||||||
|
|
||||||
|
Common pattern for centering absolutely positioned elements:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
|
||||||
|
Centered
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Z-axis translation
|
||||||
|
|
||||||
|
Use `translate-z-<number>` for 3D translation (requires `transform-3d` on parent):
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="transform-3d">
|
||||||
|
<div class="translate-z-4">Forward in 3D space</div>
|
||||||
|
<div class="-translate-z-8">Backward in 3D space</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom values
|
||||||
|
|
||||||
|
Use arbitrary values for custom translations:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="translate-[3.142rad]">Custom translation</div>
|
||||||
|
<div class="translate-x-[117px]">Custom pixel value</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- `translate-*` moves on both x and y axes
|
||||||
|
- `translate-x-*` moves horizontally (right = positive)
|
||||||
|
- `translate-y-*` moves vertically (down = positive)
|
||||||
|
- `translate-z-*` moves in 3D space (forward = positive)
|
||||||
|
- Negative values use dash prefix: `-translate-4`, `-translate-x-8`
|
||||||
|
- Percentages are relative to element's own size
|
||||||
|
- Common centering: `top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2`
|
||||||
|
- Z-axis requires `transform-3d` utility on parent element
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/translate
|
||||||
|
-->
|
||||||
118
.agents/skills/tailwindcss/references/typography-font-text.md
Normal file
118
.agents/skills/tailwindcss/references/typography-font-text.md
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
---
|
||||||
|
name: typography-font-text
|
||||||
|
description: Font size, weight, style, text color, line height, letter spacing, and text decoration
|
||||||
|
---
|
||||||
|
|
||||||
|
# Typography: Font & Text
|
||||||
|
|
||||||
|
Utilities for font styling, text color, line height, letter spacing, and decoration.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Font size
|
||||||
|
|
||||||
|
```html
|
||||||
|
<p class="text-xs">Extra small</p>
|
||||||
|
<p class="text-sm">Small</p>
|
||||||
|
<p class="text-base">Base (default)</p>
|
||||||
|
<p class="text-lg">Large</p>
|
||||||
|
<p class="text-xl">Extra large</p>
|
||||||
|
<p class="text-2xl">2xl</p>
|
||||||
|
<p class="text-[17px]">Arbitrary</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Font weight
|
||||||
|
|
||||||
|
```html
|
||||||
|
<p class="font-thin">100</p>
|
||||||
|
<p class="font-light">300</p>
|
||||||
|
<p class="font-normal">400</p>
|
||||||
|
<p class="font-medium">500</p>
|
||||||
|
<p class="font-semibold">600</p>
|
||||||
|
<p class="font-bold">700</p>
|
||||||
|
<p class="font-extrabold">800</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Font style and family
|
||||||
|
|
||||||
|
```html
|
||||||
|
<p class="italic">Italic</p>
|
||||||
|
<p class="not-italic">Not italic</p>
|
||||||
|
<p class="font-sans">Sans (default)</p>
|
||||||
|
<p class="font-serif">Serif</p>
|
||||||
|
<p class="font-mono">Monospace</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Text color
|
||||||
|
|
||||||
|
```html
|
||||||
|
<p class="text-gray-900">Dark text</p>
|
||||||
|
<p class="text-blue-500">Blue</p>
|
||||||
|
<p class="text-red-500/50">50% opacity</p>
|
||||||
|
<p class="text-[#1da1f2]">Arbitrary color</p>
|
||||||
|
<p class="text-(--my-color)">CSS variable</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Line height
|
||||||
|
|
||||||
|
```html
|
||||||
|
<p class="leading-none">1</p>
|
||||||
|
<p class="leading-tight">1.25</p>
|
||||||
|
<p class="leading-normal">1.5</p>
|
||||||
|
<p class="leading-relaxed">1.625</p>
|
||||||
|
<p class="leading-loose">2</p>
|
||||||
|
<p class="leading-[3rem]">Arbitrary</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Letter spacing
|
||||||
|
|
||||||
|
```html
|
||||||
|
<p class="tracking-tighter">-0.05em</p>
|
||||||
|
<p class="tracking-tight">-0.025em</p>
|
||||||
|
<p class="tracking-normal">0</p>
|
||||||
|
<p class="tracking-wide">0.025em</p>
|
||||||
|
<p class="tracking-widest">0.1em</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Text decoration
|
||||||
|
|
||||||
|
```html
|
||||||
|
<p class="underline">Underline</p>
|
||||||
|
<p class="line-through">Strikethrough</p>
|
||||||
|
<p class="no-underline">Remove</p>
|
||||||
|
<p class="overline">Overline</p>
|
||||||
|
<p class="underline decoration-2 underline-offset-4">Custom</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Text transform and overflow
|
||||||
|
|
||||||
|
```html
|
||||||
|
<p class="uppercase">UPPERCASE</p>
|
||||||
|
<p class="lowercase">lowercase</p>
|
||||||
|
<p class="capitalize">Capitalize Each</p>
|
||||||
|
<p class="normal-case">Normal</p>
|
||||||
|
|
||||||
|
<p class="truncate">Single line ellipsis</p>
|
||||||
|
<p class="text-ellipsis overflow-hidden">Ellipsis</p>
|
||||||
|
<p class="line-clamp-3">Clamp to 3 lines</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Font size: `text-xs` through `text-9xl`, theme scale
|
||||||
|
- Weight: `font-thin` to `font-black`
|
||||||
|
- Color: `text-{color}-{shade}`, opacity modifier `/50`, arbitrary `text-[#hex]`
|
||||||
|
- Line height: `leading-none`, `leading-tight`, `leading-normal`, `leading-loose`
|
||||||
|
- Letter spacing: `tracking-tighter` to `tracking-widest`
|
||||||
|
- Decoration: `underline`, `line-through`, `no-underline`, `decoration-*`, `underline-offset-*`
|
||||||
|
- Overflow: `truncate` (ellipsis), `line-clamp-{n}`
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/font-size
|
||||||
|
- https://tailwindcss.com/docs/font-weight
|
||||||
|
- https://tailwindcss.com/docs/text-color
|
||||||
|
- https://tailwindcss.com/docs/line-height
|
||||||
|
- https://tailwindcss.com/docs/letter-spacing
|
||||||
|
- https://tailwindcss.com/docs/text-decoration
|
||||||
|
-->
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
---
|
||||||
|
name: typography-list-style
|
||||||
|
description: Controlling list marker style and position with list-style-type and list-style-position
|
||||||
|
---
|
||||||
|
|
||||||
|
# List Style
|
||||||
|
|
||||||
|
Utilities for controlling the marker style and position of list items.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### List style type
|
||||||
|
|
||||||
|
```html
|
||||||
|
<ul class="list-disc">
|
||||||
|
<li>Disc bullets (default for ul)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ol class="list-decimal">
|
||||||
|
<li>Decimal numbers (default for ol)</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<ul class="list-none">
|
||||||
|
<li>No markers (often with custom bullets via before/after)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!-- Custom value -->
|
||||||
|
<ol class="list-[upper-roman]">Roman numerals</ol>
|
||||||
|
<ul class="list-(--my-marker)">Custom property</ul>
|
||||||
|
```
|
||||||
|
|
||||||
|
### List style position
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Bullets inside content flow -->
|
||||||
|
<ul class="list-inside list-disc">
|
||||||
|
<li>5 cups chopped Porcini mushrooms</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!-- Bullets outside content flow (default) -->
|
||||||
|
<ul class="list-outside list-disc">
|
||||||
|
<li>5 cups chopped Porcini mushrooms</li>
|
||||||
|
</ul>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Responsive
|
||||||
|
|
||||||
|
```html
|
||||||
|
<ul class="list-none md:list-disc md:list-outside">...</ul>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- `list-disc` - disc bullets (ul default)
|
||||||
|
- `list-decimal` - decimal numbers (ol default)
|
||||||
|
- `list-none` - no markers
|
||||||
|
- Custom: `list-[upper-roman]`, `list-[lower-alpha]`, `list-(--var)`
|
||||||
|
- `list-inside` - markers inside content box
|
||||||
|
- `list-outside` - markers outside content box (default)
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/list-style-type
|
||||||
|
- https://tailwindcss.com/docs/list-style-position
|
||||||
|
-->
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
name: typography-text-align
|
||||||
|
description: Controlling text alignment with left, center, right, justify, and logical properties
|
||||||
|
---
|
||||||
|
|
||||||
|
# Text Align
|
||||||
|
|
||||||
|
Utilities for controlling the alignment of text.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Basic alignment
|
||||||
|
|
||||||
|
Use `text-left`, `text-center`, `text-right`, `text-justify`:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<p class="text-left">Left aligned</p>
|
||||||
|
<p class="text-center">Center aligned</p>
|
||||||
|
<p class="text-right">Right aligned</p>
|
||||||
|
<p class="text-justify">Justified text</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logical properties
|
||||||
|
|
||||||
|
Use `text-start` and `text-end` for RTL-aware alignment:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div dir="ltr">
|
||||||
|
<p class="text-start">Left in LTR</p>
|
||||||
|
<p class="text-end">Right in LTR</p>
|
||||||
|
</div>
|
||||||
|
<div dir="rtl">
|
||||||
|
<p class="text-start">Right in RTL</p>
|
||||||
|
<p class="text-end">Left in RTL</p>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Responsive alignment
|
||||||
|
|
||||||
|
```html
|
||||||
|
<p class="text-left md:text-center lg:text-right">
|
||||||
|
Responsive alignment
|
||||||
|
</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- `text-left` - aligns to left edge
|
||||||
|
- `text-center` - centers text
|
||||||
|
- `text-right` - aligns to right edge
|
||||||
|
- `text-justify` - justifies text (spaces words evenly)
|
||||||
|
- `text-start` - aligns to start (left in LTR, right in RTL)
|
||||||
|
- `text-end` - aligns to end (right in LTR, left in RTL)
|
||||||
|
- Use logical properties (`text-start`, `text-end`) for internationalization
|
||||||
|
- Common pattern: `text-center` for headings, `text-left` for body text
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/text-align
|
||||||
|
-->
|
||||||
76
.agents/skills/tailwindcss/references/visual-background.md
Normal file
76
.agents/skills/tailwindcss/references/visual-background.md
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
---
|
||||||
|
name: visual-background
|
||||||
|
description: Background color, gradient, image, and attachment utilities
|
||||||
|
---
|
||||||
|
|
||||||
|
# Background
|
||||||
|
|
||||||
|
Utilities for background color, gradients, images, and attachment.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Background color
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="bg-white">White</div>
|
||||||
|
<div class="bg-gray-100">Light gray</div>
|
||||||
|
<div class="bg-blue-500">Blue</div>
|
||||||
|
<div class="bg-sky-500/50">50% opacity</div>
|
||||||
|
<div class="bg-[#1da1f2]">Arbitrary</div>
|
||||||
|
<div class="bg-(--my-bg)">CSS variable</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Color palette follows theme (red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose, slate, gray, zinc, neutral, stone).
|
||||||
|
|
||||||
|
### Background image and gradient
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="bg-gradient-to-r from-blue-500 to-purple-600">Linear gradient</div>
|
||||||
|
<div class="bg-gradient-to-br from-indigo-500 via-purple-500 to-pink-500">Multi-stop</div>
|
||||||
|
|
||||||
|
<div class="bg-[url('/img/hero.jpg')]">Image</div>
|
||||||
|
<div class="bg-[url('/img/hero.jpg')] bg-cover bg-center">Image with size/position</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Gradient directions: `to-t`, `to-tr`, `to-r`, `to-br`, `to-b`, `to-bl`, `to-l`, `to-tl`.
|
||||||
|
|
||||||
|
### Background size and position
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="bg-auto">auto</div>
|
||||||
|
<div class="bg-cover">cover</div>
|
||||||
|
<div class="bg-contain">contain</div>
|
||||||
|
|
||||||
|
<div class="bg-center">center</div>
|
||||||
|
<div class="bg-top">top</div>
|
||||||
|
<div class="bg-bottom">bottom</div>
|
||||||
|
<div class="bg-left">left</div>
|
||||||
|
<div class="bg-right">right</div>
|
||||||
|
<div class="bg-[position:2rem_2rem]">Arbitrary</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Background repeat and attachment
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="bg-repeat">repeat (default)</div>
|
||||||
|
<div class="bg-no-repeat">no-repeat</div>
|
||||||
|
<div class="bg-repeat-x">repeat-x</div>
|
||||||
|
|
||||||
|
<div class="bg-fixed">fixed (parallax)</div>
|
||||||
|
<div class="bg-local">local</div>
|
||||||
|
<div class="bg-scroll">scroll</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Colors: `bg-{color}-{shade}`, opacity `/50`, arbitrary `bg-[#hex]`
|
||||||
|
- Gradients: `bg-gradient-to-{dir}`, `from-*`, `via-*`, `to-*`
|
||||||
|
- Image: `bg-[url('...')]`, `bg-cover`, `bg-center`, etc.
|
||||||
|
- Size: `bg-auto`, `bg-cover`, `bg-contain`
|
||||||
|
- Position: `bg-center`, `bg-top`, `bg-[position:...]`
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/background-color
|
||||||
|
- https://tailwindcss.com/docs/background-image
|
||||||
|
-->
|
||||||
108
.agents/skills/tailwindcss/references/visual-border.md
Normal file
108
.agents/skills/tailwindcss/references/visual-border.md
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
---
|
||||||
|
name: visual-border
|
||||||
|
description: Border width, color, style, and border radius
|
||||||
|
---
|
||||||
|
|
||||||
|
# Border
|
||||||
|
|
||||||
|
Utilities for border width, color, style, and radius.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Border width
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="border">1px all sides</div>
|
||||||
|
<div class="border-2">2px</div>
|
||||||
|
<div class="border-0">No border</div>
|
||||||
|
|
||||||
|
<div class="border-t border-r border-b border-l">Per side</div>
|
||||||
|
<div class="border-x-2">Horizontal</div>
|
||||||
|
<div class="border-y">Vertical</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Border color
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="border border-gray-300">Gray border</div>
|
||||||
|
<div class="border-2 border-blue-500">Blue</div>
|
||||||
|
<div class="border border-red-500/50">With opacity</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Border style
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="border border-solid">Solid (default)</div>
|
||||||
|
<div class="border border-dashed">Dashed</div>
|
||||||
|
<div class="border border-dotted">Dotted</div>
|
||||||
|
<div class="border border-double">Double</div>
|
||||||
|
<div class="border border-none">None</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Border radius
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="rounded">Small (default)</div>
|
||||||
|
<div class="rounded-sm">2px</div>
|
||||||
|
<div class="rounded-md">6px</div>
|
||||||
|
<div class="rounded-lg">8px</div>
|
||||||
|
<div class="rounded-xl">12px</div>
|
||||||
|
<div class="rounded-2xl">16px</div>
|
||||||
|
<div class="rounded-full">Pill/circle</div>
|
||||||
|
<div class="rounded-none">0</div>
|
||||||
|
|
||||||
|
<div class="rounded-t-lg rounded-b-none">Per corner</div>
|
||||||
|
<div class="rounded-s-md rounded-e-xl">Logical (start/end)</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Divide (between children)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="divide-y divide-gray-200">
|
||||||
|
<div>Item 1</div>
|
||||||
|
<div>Item 2</div>
|
||||||
|
<div>Item 3</div>
|
||||||
|
</div>
|
||||||
|
<div class="divide-x divide-gray-300 flex">
|
||||||
|
<div>Col 1</div>
|
||||||
|
<div>Col 2</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ring (focus outline)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<button class="ring-2 ring-blue-500 ring-offset-2">Ring</button>
|
||||||
|
<button class="focus:ring-3 focus:ring-blue-500">Focus ring</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Outline
|
||||||
|
|
||||||
|
Separate from border; used for focus states. In v4: `outline` = 1px; `outline-2`, `outline-4` for width. Use `outline-offset-2` for offset.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<button class="outline outline-offset-2 outline-sky-500">Outline</button>
|
||||||
|
<button class="focus:outline-2 focus:outline-cyan-500">Focus outline</button>
|
||||||
|
<!-- Accessibility: invisible but visible in forced-colors -->
|
||||||
|
<button class="focus:outline-hidden">Focus outline-hidden</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
v4: `outline-none` = `outline-style: none`; `outline-hidden` = invisible but shows in forced-colors mode.
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Width: `border`, `border-{0,2,4,8}`, `border-{t,r,b,l,x,y}`
|
||||||
|
- Color: `border-{color}`, opacity modifier
|
||||||
|
- Radius: `rounded-{size}`, `rounded-full`, `rounded-{t,r,b,l,s,e}-*`, logical `rounded-s-*`, `rounded-e-*`
|
||||||
|
- Divide: `divide-{x,y}`, `divide-{color}` for borders between flex/grid children
|
||||||
|
- Ring: `ring`, `ring-{n}`, `ring-{color}`, `ring-offset-{n}` (v4 default ring = 1px; use `ring-3` for 3px)
|
||||||
|
- Outline: `outline`, `outline-{n}`, `outline-{color}`, `outline-offset-{n}`, `outline-hidden`, `outline-none`
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/border-width
|
||||||
|
- https://tailwindcss.com/docs/border-color
|
||||||
|
- https://tailwindcss.com/docs/border-radius
|
||||||
|
- https://tailwindcss.com/docs/divide-width
|
||||||
|
- https://tailwindcss.com/docs/ring-width
|
||||||
|
-->
|
||||||
90
.agents/skills/tailwindcss/references/visual-effects.md
Normal file
90
.agents/skills/tailwindcss/references/visual-effects.md
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
---
|
||||||
|
name: visual-effects
|
||||||
|
description: Box shadow, opacity, mix-blend, and filter effects
|
||||||
|
---
|
||||||
|
|
||||||
|
# Effects
|
||||||
|
|
||||||
|
Utilities for box shadow, opacity, mix-blend, and filters.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Box shadow
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="shadow-sm">Small</div>
|
||||||
|
<div class="shadow">Default</div>
|
||||||
|
<div class="shadow-md">Medium</div>
|
||||||
|
<div class="shadow-lg">Large</div>
|
||||||
|
<div class="shadow-xl">Extra large</div>
|
||||||
|
<div class="shadow-2xl">2xl</div>
|
||||||
|
<div class="shadow-none">None</div>
|
||||||
|
|
||||||
|
<div class="shadow-lg shadow-blue-500/50">Colored shadow</div>
|
||||||
|
<div class="shadow-[0_0_15px_rgba(0,0,0,0.2)]">Arbitrary</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Opacity
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="opacity-0">Invisible</div>
|
||||||
|
<div class="opacity-50">50%</div>
|
||||||
|
<div class="opacity-100">Full</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mix blend mode
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="mix-blend-normal">Normal</div>
|
||||||
|
<div class="mix-blend-multiply">Multiply</div>
|
||||||
|
<div class="mix-blend-screen">Screen</div>
|
||||||
|
<div class="mix-blend-overlay">Overlay</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backdrop blur and filter
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="backdrop-blur-sm">Blur backdrop</div>
|
||||||
|
<div class="backdrop-blur-md">Medium blur</div>
|
||||||
|
<div class="backdrop-blur-none">No blur</div>
|
||||||
|
|
||||||
|
<div class="backdrop-opacity-50">Backdrop opacity</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filter (blur, brightness, contrast, etc.)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="blur-sm">Blur</div>
|
||||||
|
<div class="brightness-90">Brightness</div>
|
||||||
|
<div class="contrast-125">Contrast</div>
|
||||||
|
<div class="grayscale">Grayscale</div>
|
||||||
|
<div class="invert">Invert</div>
|
||||||
|
<div class="sepia">Sepia</div>
|
||||||
|
<div class="blur-none">No filter</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Object fit (images/video)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<img class="object-contain" /> <!-- Fit within bounds -->
|
||||||
|
<img class="object-cover" /> <!-- Cover area, may crop -->
|
||||||
|
<img class="object-fill" /> <!-- Stretch -->
|
||||||
|
<img class="object-none" /> <!-- No resize -->
|
||||||
|
<img class="object-scale-down" /> <!-- contain or none, whichever is smaller -->
|
||||||
|
<img class="object-top object-cover" /> <!-- Position -->
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Shadow: `shadow-{size}`, `shadow-{color}/opacity`, `shadow-none`
|
||||||
|
- Opacity: `opacity-{0-100}`
|
||||||
|
- Mix blend: `mix-blend-{mode}`
|
||||||
|
- Backdrop: `backdrop-blur-*`, `backdrop-opacity-*`
|
||||||
|
- Filter: `blur-*`, `brightness-*`, `contrast-*`, `grayscale`, `invert`, `sepia`
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/box-shadow
|
||||||
|
- https://tailwindcss.com/docs/opacity
|
||||||
|
- https://tailwindcss.com/docs/backdrop-blur
|
||||||
|
-->
|
||||||
82
.agents/skills/tailwindcss/references/visual-svg.md
Normal file
82
.agents/skills/tailwindcss/references/visual-svg.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
---
|
||||||
|
name: visual-svg
|
||||||
|
description: Styling SVG elements with fill, stroke, and stroke-width utilities
|
||||||
|
---
|
||||||
|
|
||||||
|
# SVG Styling
|
||||||
|
|
||||||
|
Utilities for styling SVG fill and stroke. Essential when working with icon sets like Heroicons.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Fill
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Theme colors -->
|
||||||
|
<svg class="fill-blue-500">...</svg>
|
||||||
|
<svg class="fill-indigo-500 hover:fill-indigo-600">...</svg>
|
||||||
|
|
||||||
|
<!-- Inherit from text color -->
|
||||||
|
<button class="text-indigo-600 hover:text-white">
|
||||||
|
<svg class="size-5 fill-current">...</svg>
|
||||||
|
Check for updates
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Special values -->
|
||||||
|
<svg class="fill-none">...</svg>
|
||||||
|
<svg class="fill-inherit">...</svg>
|
||||||
|
<svg class="fill-transparent">...</svg>
|
||||||
|
|
||||||
|
<!-- Custom -->
|
||||||
|
<svg class="fill-[#243c5a]">...</svg>
|
||||||
|
<svg class="fill-(--my-fill)">...</svg>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stroke
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Theme colors -->
|
||||||
|
<svg class="stroke-cyan-500" fill="none">...</svg>
|
||||||
|
|
||||||
|
<!-- Inherit from text -->
|
||||||
|
<button class="text-pink-600 hover:text-white">
|
||||||
|
<svg class="size-5 stroke-current" fill="none">...</svg>
|
||||||
|
Download
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Special values -->
|
||||||
|
<svg class="stroke-none stroke-inherit stroke-transparent">...</svg>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stroke width
|
||||||
|
|
||||||
|
```html
|
||||||
|
<svg class="stroke-1">Thin stroke</svg>
|
||||||
|
<svg class="stroke-2">Medium stroke</svg>
|
||||||
|
<svg class="stroke-[1.5]">Custom width</svg>
|
||||||
|
<svg class="stroke-(length:--my-stroke)">Custom property</svg>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Combined
|
||||||
|
|
||||||
|
```html
|
||||||
|
<svg class="size-6 fill-blue-500 stroke-blue-700 stroke-2" fill="none">
|
||||||
|
<!-- Outlined icon with colored stroke -->
|
||||||
|
</svg>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- `fill-*` / `stroke-*` - all theme colors (e.g. `fill-red-500`)
|
||||||
|
- `fill-current` / `stroke-current` - use current text color (common for icons in buttons)
|
||||||
|
- `fill-none` / `stroke-none` - no fill/stroke
|
||||||
|
- `stroke-1`, `stroke-2`, etc. - stroke width (number = px)
|
||||||
|
- Custom: `fill-[#hex]`, `stroke-(--var)`, `stroke-[1.5]`
|
||||||
|
- Use with variants: `hover:fill-blue-600`, `md:stroke-2`
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://tailwindcss.com/docs/fill
|
||||||
|
- https://tailwindcss.com/docs/stroke
|
||||||
|
- https://tailwindcss.com/docs/stroke-width
|
||||||
|
-->
|
||||||
3254
.agents/skills/vercel-react-best-practices/AGENTS.md
Normal file
3254
.agents/skills/vercel-react-best-practices/AGENTS.md
Normal file
File diff suppressed because it is too large
Load Diff
123
.agents/skills/vercel-react-best-practices/README.md
Normal file
123
.agents/skills/vercel-react-best-practices/README.md
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
# React Best Practices
|
||||||
|
|
||||||
|
A structured repository for creating and maintaining React Best Practices optimized for agents and LLMs.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
- `rules/` - Individual rule files (one per rule)
|
||||||
|
- `_sections.md` - Section metadata (titles, impacts, descriptions)
|
||||||
|
- `_template.md` - Template for creating new rules
|
||||||
|
- `area-description.md` - Individual rule files
|
||||||
|
- `src/` - Build scripts and utilities
|
||||||
|
- `metadata.json` - Document metadata (version, organization, abstract)
|
||||||
|
- __`AGENTS.md`__ - Compiled output (generated)
|
||||||
|
- __`test-cases.json`__ - Test cases for LLM evaluation (generated)
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
1. Install dependencies:
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Build AGENTS.md from rules:
|
||||||
|
```bash
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Validate rule files:
|
||||||
|
```bash
|
||||||
|
pnpm validate
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Extract test cases:
|
||||||
|
```bash
|
||||||
|
pnpm extract-tests
|
||||||
|
```
|
||||||
|
|
||||||
|
## Creating a New Rule
|
||||||
|
|
||||||
|
1. Copy `rules/_template.md` to `rules/area-description.md`
|
||||||
|
2. Choose the appropriate area prefix:
|
||||||
|
- `async-` for Eliminating Waterfalls (Section 1)
|
||||||
|
- `bundle-` for Bundle Size Optimization (Section 2)
|
||||||
|
- `server-` for Server-Side Performance (Section 3)
|
||||||
|
- `client-` for Client-Side Data Fetching (Section 4)
|
||||||
|
- `rerender-` for Re-render Optimization (Section 5)
|
||||||
|
- `rendering-` for Rendering Performance (Section 6)
|
||||||
|
- `js-` for JavaScript Performance (Section 7)
|
||||||
|
- `advanced-` for Advanced Patterns (Section 8)
|
||||||
|
3. Fill in the frontmatter and content
|
||||||
|
4. Ensure you have clear examples with explanations
|
||||||
|
5. Run `pnpm build` to regenerate AGENTS.md and test-cases.json
|
||||||
|
|
||||||
|
## Rule File Structure
|
||||||
|
|
||||||
|
Each rule file should follow this structure:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
title: Rule Title Here
|
||||||
|
impact: MEDIUM
|
||||||
|
impactDescription: Optional description
|
||||||
|
tags: tag1, tag2, tag3
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rule Title Here
|
||||||
|
|
||||||
|
Brief explanation of the rule and why it matters.
|
||||||
|
|
||||||
|
**Incorrect (description of what's wrong):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Bad code example
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (description of what's right):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Good code example
|
||||||
|
```
|
||||||
|
|
||||||
|
Optional explanatory text after examples.
|
||||||
|
|
||||||
|
Reference: [Link](https://example.com)
|
||||||
|
|
||||||
|
## File Naming Convention
|
||||||
|
|
||||||
|
- Files starting with `_` are special (excluded from build)
|
||||||
|
- Rule files: `area-description.md` (e.g., `async-parallel.md`)
|
||||||
|
- Section is automatically inferred from filename prefix
|
||||||
|
- Rules are sorted alphabetically by title within each section
|
||||||
|
- IDs (e.g., 1.1, 1.2) are auto-generated during build
|
||||||
|
|
||||||
|
## Impact Levels
|
||||||
|
|
||||||
|
- `CRITICAL` - Highest priority, major performance gains
|
||||||
|
- `HIGH` - Significant performance improvements
|
||||||
|
- `MEDIUM-HIGH` - Moderate-high gains
|
||||||
|
- `MEDIUM` - Moderate performance improvements
|
||||||
|
- `LOW-MEDIUM` - Low-medium gains
|
||||||
|
- `LOW` - Incremental improvements
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
|
||||||
|
- `pnpm build` - Compile rules into AGENTS.md
|
||||||
|
- `pnpm validate` - Validate all rule files
|
||||||
|
- `pnpm extract-tests` - Extract test cases for LLM evaluation
|
||||||
|
- `pnpm dev` - Build and validate
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
When adding or modifying rules:
|
||||||
|
|
||||||
|
1. Use the correct filename prefix for your section
|
||||||
|
2. Follow the `_template.md` structure
|
||||||
|
3. Include clear bad/good examples with explanations
|
||||||
|
4. Add appropriate tags
|
||||||
|
5. Run `pnpm build` to regenerate AGENTS.md and test-cases.json
|
||||||
|
6. Rules are automatically sorted by title - no need to manage numbers!
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
|
Originally created by [@shuding](https://x.com/shuding) at [Vercel](https://vercel.com).
|
||||||
141
.agents/skills/vercel-react-best-practices/SKILL.md
Normal file
141
.agents/skills/vercel-react-best-practices/SKILL.md
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
---
|
||||||
|
name: vercel-react-best-practices
|
||||||
|
description: React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.
|
||||||
|
license: MIT
|
||||||
|
metadata:
|
||||||
|
author: vercel
|
||||||
|
version: "1.0.0"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Vercel React Best Practices
|
||||||
|
|
||||||
|
Comprehensive performance optimization guide for React and Next.js applications, maintained by Vercel. Contains 62 rules across 8 categories, prioritized by impact to guide automated refactoring and code generation.
|
||||||
|
|
||||||
|
## When to Apply
|
||||||
|
|
||||||
|
Reference these guidelines when:
|
||||||
|
- Writing new React components or Next.js pages
|
||||||
|
- Implementing data fetching (client or server-side)
|
||||||
|
- Reviewing code for performance issues
|
||||||
|
- Refactoring existing React/Next.js code
|
||||||
|
- Optimizing bundle size or load times
|
||||||
|
|
||||||
|
## Rule Categories by Priority
|
||||||
|
|
||||||
|
| Priority | Category | Impact | Prefix |
|
||||||
|
|----------|----------|--------|--------|
|
||||||
|
| 1 | Eliminating Waterfalls | CRITICAL | `async-` |
|
||||||
|
| 2 | Bundle Size Optimization | CRITICAL | `bundle-` |
|
||||||
|
| 3 | Server-Side Performance | HIGH | `server-` |
|
||||||
|
| 4 | Client-Side Data Fetching | MEDIUM-HIGH | `client-` |
|
||||||
|
| 5 | Re-render Optimization | MEDIUM | `rerender-` |
|
||||||
|
| 6 | Rendering Performance | MEDIUM | `rendering-` |
|
||||||
|
| 7 | JavaScript Performance | LOW-MEDIUM | `js-` |
|
||||||
|
| 8 | Advanced Patterns | LOW | `advanced-` |
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
### 1. Eliminating Waterfalls (CRITICAL)
|
||||||
|
|
||||||
|
- `async-defer-await` - Move await into branches where actually used
|
||||||
|
- `async-parallel` - Use Promise.all() for independent operations
|
||||||
|
- `async-dependencies` - Use better-all for partial dependencies
|
||||||
|
- `async-api-routes` - Start promises early, await late in API routes
|
||||||
|
- `async-suspense-boundaries` - Use Suspense to stream content
|
||||||
|
|
||||||
|
### 2. Bundle Size Optimization (CRITICAL)
|
||||||
|
|
||||||
|
- `bundle-barrel-imports` - Import directly, avoid barrel files
|
||||||
|
- `bundle-dynamic-imports` - Use next/dynamic for heavy components
|
||||||
|
- `bundle-defer-third-party` - Load analytics/logging after hydration
|
||||||
|
- `bundle-conditional` - Load modules only when feature is activated
|
||||||
|
- `bundle-preload` - Preload on hover/focus for perceived speed
|
||||||
|
|
||||||
|
### 3. Server-Side Performance (HIGH)
|
||||||
|
|
||||||
|
- `server-auth-actions` - Authenticate server actions like API routes
|
||||||
|
- `server-cache-react` - Use React.cache() for per-request deduplication
|
||||||
|
- `server-cache-lru` - Use LRU cache for cross-request caching
|
||||||
|
- `server-dedup-props` - Avoid duplicate serialization in RSC props
|
||||||
|
- `server-hoist-static-io` - Hoist static I/O (fonts, logos) to module level
|
||||||
|
- `server-serialization` - Minimize data passed to client components
|
||||||
|
- `server-parallel-fetching` - Restructure components to parallelize fetches
|
||||||
|
- `server-after-nonblocking` - Use after() for non-blocking operations
|
||||||
|
|
||||||
|
### 4. Client-Side Data Fetching (MEDIUM-HIGH)
|
||||||
|
|
||||||
|
- `client-swr-dedup` - Use SWR for automatic request deduplication
|
||||||
|
- `client-event-listeners` - Deduplicate global event listeners
|
||||||
|
- `client-passive-event-listeners` - Use passive listeners for scroll
|
||||||
|
- `client-localstorage-schema` - Version and minimize localStorage data
|
||||||
|
|
||||||
|
### 5. Re-render Optimization (MEDIUM)
|
||||||
|
|
||||||
|
- `rerender-defer-reads` - Don't subscribe to state only used in callbacks
|
||||||
|
- `rerender-memo` - Extract expensive work into memoized components
|
||||||
|
- `rerender-memo-with-default-value` - Hoist default non-primitive props
|
||||||
|
- `rerender-dependencies` - Use primitive dependencies in effects
|
||||||
|
- `rerender-derived-state` - Subscribe to derived booleans, not raw values
|
||||||
|
- `rerender-derived-state-no-effect` - Derive state during render, not effects
|
||||||
|
- `rerender-functional-setstate` - Use functional setState for stable callbacks
|
||||||
|
- `rerender-lazy-state-init` - Pass function to useState for expensive values
|
||||||
|
- `rerender-simple-expression-in-memo` - Avoid memo for simple primitives
|
||||||
|
- `rerender-move-effect-to-event` - Put interaction logic in event handlers
|
||||||
|
- `rerender-transitions` - Use startTransition for non-urgent updates
|
||||||
|
- `rerender-use-ref-transient-values` - Use refs for transient frequent values
|
||||||
|
- `rerender-no-inline-components` - Don't define components inside components
|
||||||
|
|
||||||
|
### 6. Rendering Performance (MEDIUM)
|
||||||
|
|
||||||
|
- `rendering-animate-svg-wrapper` - Animate div wrapper, not SVG element
|
||||||
|
- `rendering-content-visibility` - Use content-visibility for long lists
|
||||||
|
- `rendering-hoist-jsx` - Extract static JSX outside components
|
||||||
|
- `rendering-svg-precision` - Reduce SVG coordinate precision
|
||||||
|
- `rendering-hydration-no-flicker` - Use inline script for client-only data
|
||||||
|
- `rendering-hydration-suppress-warning` - Suppress expected mismatches
|
||||||
|
- `rendering-activity` - Use Activity component for show/hide
|
||||||
|
- `rendering-conditional-render` - Use ternary, not && for conditionals
|
||||||
|
- `rendering-usetransition-loading` - Prefer useTransition for loading state
|
||||||
|
- `rendering-resource-hints` - Use React DOM resource hints for preloading
|
||||||
|
- `rendering-script-defer-async` - Use defer or async on script tags
|
||||||
|
|
||||||
|
### 7. JavaScript Performance (LOW-MEDIUM)
|
||||||
|
|
||||||
|
- `js-batch-dom-css` - Group CSS changes via classes or cssText
|
||||||
|
- `js-index-maps` - Build Map for repeated lookups
|
||||||
|
- `js-cache-property-access` - Cache object properties in loops
|
||||||
|
- `js-cache-function-results` - Cache function results in module-level Map
|
||||||
|
- `js-cache-storage` - Cache localStorage/sessionStorage reads
|
||||||
|
- `js-combine-iterations` - Combine multiple filter/map into one loop
|
||||||
|
- `js-length-check-first` - Check array length before expensive comparison
|
||||||
|
- `js-early-exit` - Return early from functions
|
||||||
|
- `js-hoist-regexp` - Hoist RegExp creation outside loops
|
||||||
|
- `js-min-max-loop` - Use loop for min/max instead of sort
|
||||||
|
- `js-set-map-lookups` - Use Set/Map for O(1) lookups
|
||||||
|
- `js-tosorted-immutable` - Use toSorted() for immutability
|
||||||
|
- `js-flatmap-filter` - Use flatMap to map and filter in one pass
|
||||||
|
|
||||||
|
### 8. Advanced Patterns (LOW)
|
||||||
|
|
||||||
|
- `advanced-event-handler-refs` - Store event handlers in refs
|
||||||
|
- `advanced-init-once` - Initialize app once per app load
|
||||||
|
- `advanced-use-latest` - useLatest for stable callback refs
|
||||||
|
|
||||||
|
## How to Use
|
||||||
|
|
||||||
|
Read individual rule files for detailed explanations and code examples:
|
||||||
|
|
||||||
|
```
|
||||||
|
rules/async-parallel.md
|
||||||
|
rules/bundle-barrel-imports.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Each rule file contains:
|
||||||
|
- Brief explanation of why it matters
|
||||||
|
- Incorrect code example with explanation
|
||||||
|
- Correct code example with explanation
|
||||||
|
- Additional context and references
|
||||||
|
|
||||||
|
## Full Compiled Document
|
||||||
|
|
||||||
|
For the complete guide with all rules expanded: `AGENTS.md`
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
---
|
||||||
|
title: Store Event Handlers in Refs
|
||||||
|
impact: LOW
|
||||||
|
impactDescription: stable subscriptions
|
||||||
|
tags: advanced, hooks, refs, event-handlers, optimization
|
||||||
|
---
|
||||||
|
|
||||||
|
## Store Event Handlers in Refs
|
||||||
|
|
||||||
|
Store callbacks in refs when used in effects that shouldn't re-subscribe on callback changes.
|
||||||
|
|
||||||
|
**Incorrect (re-subscribes on every render):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function useWindowEvent(event: string, handler: (e) => void) {
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener(event, handler)
|
||||||
|
return () => window.removeEventListener(event, handler)
|
||||||
|
}, [event, handler])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (stable subscription):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function useWindowEvent(event: string, handler: (e) => void) {
|
||||||
|
const handlerRef = useRef(handler)
|
||||||
|
useEffect(() => {
|
||||||
|
handlerRef.current = handler
|
||||||
|
}, [handler])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const listener = (e) => handlerRef.current(e)
|
||||||
|
window.addEventListener(event, listener)
|
||||||
|
return () => window.removeEventListener(event, listener)
|
||||||
|
}, [event])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Alternative: use `useEffectEvent` if you're on latest React:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useEffectEvent } from 'react'
|
||||||
|
|
||||||
|
function useWindowEvent(event: string, handler: (e) => void) {
|
||||||
|
const onEvent = useEffectEvent(handler)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener(event, onEvent)
|
||||||
|
return () => window.removeEventListener(event, onEvent)
|
||||||
|
}, [event])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`useEffectEvent` provides a cleaner API for the same pattern: it creates a stable function reference that always calls the latest version of the handler.
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
title: Initialize App Once, Not Per Mount
|
||||||
|
impact: LOW-MEDIUM
|
||||||
|
impactDescription: avoids duplicate init in development
|
||||||
|
tags: initialization, useEffect, app-startup, side-effects
|
||||||
|
---
|
||||||
|
|
||||||
|
## Initialize App Once, Not Per Mount
|
||||||
|
|
||||||
|
Do not put app-wide initialization that must run once per app load inside `useEffect([])` of a component. Components can remount and effects will re-run. Use a module-level guard or top-level init in the entry module instead.
|
||||||
|
|
||||||
|
**Incorrect (runs twice in dev, re-runs on remount):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function Comp() {
|
||||||
|
useEffect(() => {
|
||||||
|
loadFromStorage()
|
||||||
|
checkAuthToken()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (once per app load):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
let didInit = false
|
||||||
|
|
||||||
|
function Comp() {
|
||||||
|
useEffect(() => {
|
||||||
|
if (didInit) return
|
||||||
|
didInit = true
|
||||||
|
loadFromStorage()
|
||||||
|
checkAuthToken()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Reference: [Initializing the application](https://react.dev/learn/you-might-not-need-an-effect#initializing-the-application)
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
title: useEffectEvent for Stable Callback Refs
|
||||||
|
impact: LOW
|
||||||
|
impactDescription: prevents effect re-runs
|
||||||
|
tags: advanced, hooks, useEffectEvent, refs, optimization
|
||||||
|
---
|
||||||
|
|
||||||
|
## useEffectEvent for Stable Callback Refs
|
||||||
|
|
||||||
|
Access latest values in callbacks without adding them to dependency arrays. Prevents effect re-runs while avoiding stale closures.
|
||||||
|
|
||||||
|
**Incorrect (effect re-runs on every callback change):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
|
||||||
|
const [query, setQuery] = useState('')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timeout = setTimeout(() => onSearch(query), 300)
|
||||||
|
return () => clearTimeout(timeout)
|
||||||
|
}, [query, onSearch])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (using React's useEffectEvent):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useEffectEvent } from 'react';
|
||||||
|
|
||||||
|
function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
|
||||||
|
const [query, setQuery] = useState('')
|
||||||
|
const onSearchEvent = useEffectEvent(onSearch)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timeout = setTimeout(() => onSearchEvent(query), 300)
|
||||||
|
return () => clearTimeout(timeout)
|
||||||
|
}, [query])
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
title: Prevent Waterfall Chains in API Routes
|
||||||
|
impact: CRITICAL
|
||||||
|
impactDescription: 2-10× improvement
|
||||||
|
tags: api-routes, server-actions, waterfalls, parallelization
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prevent Waterfall Chains in API Routes
|
||||||
|
|
||||||
|
In API routes and Server Actions, start independent operations immediately, even if you don't await them yet.
|
||||||
|
|
||||||
|
**Incorrect (config waits for auth, data waits for both):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
const session = await auth()
|
||||||
|
const config = await fetchConfig()
|
||||||
|
const data = await fetchData(session.user.id)
|
||||||
|
return Response.json({ data, config })
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (auth and config start immediately):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
const sessionPromise = auth()
|
||||||
|
const configPromise = fetchConfig()
|
||||||
|
const session = await sessionPromise
|
||||||
|
const [config, data] = await Promise.all([
|
||||||
|
configPromise,
|
||||||
|
fetchData(session.user.id)
|
||||||
|
])
|
||||||
|
return Response.json({ data, config })
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For operations with more complex dependency chains, use `better-all` to automatically maximize parallelism (see Dependency-Based Parallelization).
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
---
|
||||||
|
title: Defer Await Until Needed
|
||||||
|
impact: HIGH
|
||||||
|
impactDescription: avoids blocking unused code paths
|
||||||
|
tags: async, await, conditional, optimization
|
||||||
|
---
|
||||||
|
|
||||||
|
## Defer Await Until Needed
|
||||||
|
|
||||||
|
Move `await` operations into the branches where they're actually used to avoid blocking code paths that don't need them.
|
||||||
|
|
||||||
|
**Incorrect (blocks both branches):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
async function handleRequest(userId: string, skipProcessing: boolean) {
|
||||||
|
const userData = await fetchUserData(userId)
|
||||||
|
|
||||||
|
if (skipProcessing) {
|
||||||
|
// Returns immediately but still waited for userData
|
||||||
|
return { skipped: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only this branch uses userData
|
||||||
|
return processUserData(userData)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (only blocks when needed):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
async function handleRequest(userId: string, skipProcessing: boolean) {
|
||||||
|
if (skipProcessing) {
|
||||||
|
// Returns immediately without waiting
|
||||||
|
return { skipped: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch only when needed
|
||||||
|
const userData = await fetchUserData(userId)
|
||||||
|
return processUserData(userData)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Another example (early return optimization):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Incorrect: always fetches permissions
|
||||||
|
async function updateResource(resourceId: string, userId: string) {
|
||||||
|
const permissions = await fetchPermissions(userId)
|
||||||
|
const resource = await getResource(resourceId)
|
||||||
|
|
||||||
|
if (!resource) {
|
||||||
|
return { error: 'Not found' }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!permissions.canEdit) {
|
||||||
|
return { error: 'Forbidden' }
|
||||||
|
}
|
||||||
|
|
||||||
|
return await updateResourceData(resource, permissions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Correct: fetches only when needed
|
||||||
|
async function updateResource(resourceId: string, userId: string) {
|
||||||
|
const resource = await getResource(resourceId)
|
||||||
|
|
||||||
|
if (!resource) {
|
||||||
|
return { error: 'Not found' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const permissions = await fetchPermissions(userId)
|
||||||
|
|
||||||
|
if (!permissions.canEdit) {
|
||||||
|
return { error: 'Forbidden' }
|
||||||
|
}
|
||||||
|
|
||||||
|
return await updateResourceData(resource, permissions)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This optimization is especially valuable when the skipped branch is frequently taken, or when the deferred operation is expensive.
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
title: Dependency-Based Parallelization
|
||||||
|
impact: CRITICAL
|
||||||
|
impactDescription: 2-10× improvement
|
||||||
|
tags: async, parallelization, dependencies, better-all
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dependency-Based Parallelization
|
||||||
|
|
||||||
|
For operations with partial dependencies, use `better-all` to maximize parallelism. It automatically starts each task at the earliest possible moment.
|
||||||
|
|
||||||
|
**Incorrect (profile waits for config unnecessarily):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const [user, config] = await Promise.all([
|
||||||
|
fetchUser(),
|
||||||
|
fetchConfig()
|
||||||
|
])
|
||||||
|
const profile = await fetchProfile(user.id)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (config and profile run in parallel):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { all } from 'better-all'
|
||||||
|
|
||||||
|
const { user, config, profile } = await all({
|
||||||
|
async user() { return fetchUser() },
|
||||||
|
async config() { return fetchConfig() },
|
||||||
|
async profile() {
|
||||||
|
return fetchProfile((await this.$.user).id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Alternative without extra dependencies:**
|
||||||
|
|
||||||
|
We can also create all the promises first, and do `Promise.all()` at the end.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const userPromise = fetchUser()
|
||||||
|
const profilePromise = userPromise.then(user => fetchProfile(user.id))
|
||||||
|
|
||||||
|
const [user, config, profile] = await Promise.all([
|
||||||
|
userPromise,
|
||||||
|
fetchConfig(),
|
||||||
|
profilePromise
|
||||||
|
])
|
||||||
|
```
|
||||||
|
|
||||||
|
Reference: [https://github.com/shuding/better-all](https://github.com/shuding/better-all)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
title: Promise.all() for Independent Operations
|
||||||
|
impact: CRITICAL
|
||||||
|
impactDescription: 2-10× improvement
|
||||||
|
tags: async, parallelization, promises, waterfalls
|
||||||
|
---
|
||||||
|
|
||||||
|
## Promise.all() for Independent Operations
|
||||||
|
|
||||||
|
When async operations have no interdependencies, execute them concurrently using `Promise.all()`.
|
||||||
|
|
||||||
|
**Incorrect (sequential execution, 3 round trips):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const user = await fetchUser()
|
||||||
|
const posts = await fetchPosts()
|
||||||
|
const comments = await fetchComments()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (parallel execution, 1 round trip):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const [user, posts, comments] = await Promise.all([
|
||||||
|
fetchUser(),
|
||||||
|
fetchPosts(),
|
||||||
|
fetchComments()
|
||||||
|
])
|
||||||
|
```
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
---
|
||||||
|
title: Strategic Suspense Boundaries
|
||||||
|
impact: HIGH
|
||||||
|
impactDescription: faster initial paint
|
||||||
|
tags: async, suspense, streaming, layout-shift
|
||||||
|
---
|
||||||
|
|
||||||
|
## Strategic Suspense Boundaries
|
||||||
|
|
||||||
|
Instead of awaiting data in async components before returning JSX, use Suspense boundaries to show the wrapper UI faster while data loads.
|
||||||
|
|
||||||
|
**Incorrect (wrapper blocked by data fetching):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
async function Page() {
|
||||||
|
const data = await fetchData() // Blocks entire page
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>Sidebar</div>
|
||||||
|
<div>Header</div>
|
||||||
|
<div>
|
||||||
|
<DataDisplay data={data} />
|
||||||
|
</div>
|
||||||
|
<div>Footer</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The entire layout waits for data even though only the middle section needs it.
|
||||||
|
|
||||||
|
**Correct (wrapper shows immediately, data streams in):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function Page() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>Sidebar</div>
|
||||||
|
<div>Header</div>
|
||||||
|
<div>
|
||||||
|
<Suspense fallback={<Skeleton />}>
|
||||||
|
<DataDisplay />
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
<div>Footer</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function DataDisplay() {
|
||||||
|
const data = await fetchData() // Only blocks this component
|
||||||
|
return <div>{data.content}</div>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Sidebar, Header, and Footer render immediately. Only DataDisplay waits for data.
|
||||||
|
|
||||||
|
**Alternative (share promise across components):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function Page() {
|
||||||
|
// Start fetch immediately, but don't await
|
||||||
|
const dataPromise = fetchData()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>Sidebar</div>
|
||||||
|
<div>Header</div>
|
||||||
|
<Suspense fallback={<Skeleton />}>
|
||||||
|
<DataDisplay dataPromise={dataPromise} />
|
||||||
|
<DataSummary dataPromise={dataPromise} />
|
||||||
|
</Suspense>
|
||||||
|
<div>Footer</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DataDisplay({ dataPromise }: { dataPromise: Promise<Data> }) {
|
||||||
|
const data = use(dataPromise) // Unwraps the promise
|
||||||
|
return <div>{data.content}</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
function DataSummary({ dataPromise }: { dataPromise: Promise<Data> }) {
|
||||||
|
const data = use(dataPromise) // Reuses the same promise
|
||||||
|
return <div>{data.summary}</div>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Both components share the same promise, so only one fetch occurs. Layout renders immediately while both components wait together.
|
||||||
|
|
||||||
|
**When NOT to use this pattern:**
|
||||||
|
|
||||||
|
- Critical data needed for layout decisions (affects positioning)
|
||||||
|
- SEO-critical content above the fold
|
||||||
|
- Small, fast queries where suspense overhead isn't worth it
|
||||||
|
- When you want to avoid layout shift (loading → content jump)
|
||||||
|
|
||||||
|
**Trade-off:** Faster initial paint vs potential layout shift. Choose based on your UX priorities.
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
---
|
||||||
|
title: Avoid Barrel File Imports
|
||||||
|
impact: CRITICAL
|
||||||
|
impactDescription: 200-800ms import cost, slow builds
|
||||||
|
tags: bundle, imports, tree-shaking, barrel-files, performance
|
||||||
|
---
|
||||||
|
|
||||||
|
## Avoid Barrel File Imports
|
||||||
|
|
||||||
|
Import directly from source files instead of barrel files to avoid loading thousands of unused modules. **Barrel files** are entry points that re-export multiple modules (e.g., `index.js` that does `export * from './module'`).
|
||||||
|
|
||||||
|
Popular icon and component libraries can have **up to 10,000 re-exports** in their entry file. For many React packages, **it takes 200-800ms just to import them**, affecting both development speed and production cold starts.
|
||||||
|
|
||||||
|
**Why tree-shaking doesn't help:** When a library is marked as external (not bundled), the bundler can't optimize it. If you bundle it to enable tree-shaking, builds become substantially slower analyzing the entire module graph.
|
||||||
|
|
||||||
|
**Incorrect (imports entire library):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Check, X, Menu } from 'lucide-react'
|
||||||
|
// Loads 1,583 modules, takes ~2.8s extra in dev
|
||||||
|
// Runtime cost: 200-800ms on every cold start
|
||||||
|
|
||||||
|
import { Button, TextField } from '@mui/material'
|
||||||
|
// Loads 2,225 modules, takes ~4.2s extra in dev
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (imports only what you need):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import Check from 'lucide-react/dist/esm/icons/check'
|
||||||
|
import X from 'lucide-react/dist/esm/icons/x'
|
||||||
|
import Menu from 'lucide-react/dist/esm/icons/menu'
|
||||||
|
// Loads only 3 modules (~2KB vs ~1MB)
|
||||||
|
|
||||||
|
import Button from '@mui/material/Button'
|
||||||
|
import TextField from '@mui/material/TextField'
|
||||||
|
// Loads only what you use
|
||||||
|
```
|
||||||
|
|
||||||
|
**Alternative (Next.js 13.5+):**
|
||||||
|
|
||||||
|
```js
|
||||||
|
// next.config.js - use optimizePackageImports
|
||||||
|
module.exports = {
|
||||||
|
experimental: {
|
||||||
|
optimizePackageImports: ['lucide-react', '@mui/material']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then you can keep the ergonomic barrel imports:
|
||||||
|
import { Check, X, Menu } from 'lucide-react'
|
||||||
|
// Automatically transformed to direct imports at build time
|
||||||
|
```
|
||||||
|
|
||||||
|
Direct imports provide 15-70% faster dev boot, 28% faster builds, 40% faster cold starts, and significantly faster HMR.
|
||||||
|
|
||||||
|
Libraries commonly affected: `lucide-react`, `@mui/material`, `@mui/icons-material`, `@tabler/icons-react`, `react-icons`, `@headlessui/react`, `@radix-ui/react-*`, `lodash`, `ramda`, `date-fns`, `rxjs`, `react-use`.
|
||||||
|
|
||||||
|
Reference: [How we optimized package imports in Next.js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js)
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
title: Conditional Module Loading
|
||||||
|
impact: HIGH
|
||||||
|
impactDescription: loads large data only when needed
|
||||||
|
tags: bundle, conditional-loading, lazy-loading
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conditional Module Loading
|
||||||
|
|
||||||
|
Load large data or modules only when a feature is activated.
|
||||||
|
|
||||||
|
**Example (lazy-load animation frames):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function AnimationPlayer({ enabled, setEnabled }: { enabled: boolean; setEnabled: React.Dispatch<React.SetStateAction<boolean>> }) {
|
||||||
|
const [frames, setFrames] = useState<Frame[] | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (enabled && !frames && typeof window !== 'undefined') {
|
||||||
|
import('./animation-frames.js')
|
||||||
|
.then(mod => setFrames(mod.frames))
|
||||||
|
.catch(() => setEnabled(false))
|
||||||
|
}
|
||||||
|
}, [enabled, frames, setEnabled])
|
||||||
|
|
||||||
|
if (!frames) return <Skeleton />
|
||||||
|
return <Canvas frames={frames} />
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `typeof window !== 'undefined'` check prevents bundling this module for SSR, optimizing server bundle size and build speed.
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
---
|
||||||
|
title: Defer Non-Critical Third-Party Libraries
|
||||||
|
impact: MEDIUM
|
||||||
|
impactDescription: loads after hydration
|
||||||
|
tags: bundle, third-party, analytics, defer
|
||||||
|
---
|
||||||
|
|
||||||
|
## Defer Non-Critical Third-Party Libraries
|
||||||
|
|
||||||
|
Analytics, logging, and error tracking don't block user interaction. Load them after hydration.
|
||||||
|
|
||||||
|
**Incorrect (blocks initial bundle):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Analytics } from '@vercel/analytics/react'
|
||||||
|
|
||||||
|
export default function RootLayout({ children }) {
|
||||||
|
return (
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
{children}
|
||||||
|
<Analytics />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (loads after hydration):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import dynamic from 'next/dynamic'
|
||||||
|
|
||||||
|
const Analytics = dynamic(
|
||||||
|
() => import('@vercel/analytics/react').then(m => m.Analytics),
|
||||||
|
{ ssr: false }
|
||||||
|
)
|
||||||
|
|
||||||
|
export default function RootLayout({ children }) {
|
||||||
|
return (
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
{children}
|
||||||
|
<Analytics />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
title: Dynamic Imports for Heavy Components
|
||||||
|
impact: CRITICAL
|
||||||
|
impactDescription: directly affects TTI and LCP
|
||||||
|
tags: bundle, dynamic-import, code-splitting, next-dynamic
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dynamic Imports for Heavy Components
|
||||||
|
|
||||||
|
Use `next/dynamic` to lazy-load large components not needed on initial render.
|
||||||
|
|
||||||
|
**Incorrect (Monaco bundles with main chunk ~300KB):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { MonacoEditor } from './monaco-editor'
|
||||||
|
|
||||||
|
function CodePanel({ code }: { code: string }) {
|
||||||
|
return <MonacoEditor value={code} />
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (Monaco loads on demand):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import dynamic from 'next/dynamic'
|
||||||
|
|
||||||
|
const MonacoEditor = dynamic(
|
||||||
|
() => import('./monaco-editor').then(m => m.MonacoEditor),
|
||||||
|
{ ssr: false }
|
||||||
|
)
|
||||||
|
|
||||||
|
function CodePanel({ code }: { code: string }) {
|
||||||
|
return <MonacoEditor value={code} />
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
---
|
||||||
|
title: Preload Based on User Intent
|
||||||
|
impact: MEDIUM
|
||||||
|
impactDescription: reduces perceived latency
|
||||||
|
tags: bundle, preload, user-intent, hover
|
||||||
|
---
|
||||||
|
|
||||||
|
## Preload Based on User Intent
|
||||||
|
|
||||||
|
Preload heavy bundles before they're needed to reduce perceived latency.
|
||||||
|
|
||||||
|
**Example (preload on hover/focus):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function EditorButton({ onClick }: { onClick: () => void }) {
|
||||||
|
const preload = () => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
void import('./monaco-editor')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onMouseEnter={preload}
|
||||||
|
onFocus={preload}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
Open Editor
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example (preload when feature flag is enabled):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function FlagsProvider({ children, flags }: Props) {
|
||||||
|
useEffect(() => {
|
||||||
|
if (flags.editorEnabled && typeof window !== 'undefined') {
|
||||||
|
void import('./monaco-editor').then(mod => mod.init())
|
||||||
|
}
|
||||||
|
}, [flags.editorEnabled])
|
||||||
|
|
||||||
|
return <FlagsContext.Provider value={flags}>
|
||||||
|
{children}
|
||||||
|
</FlagsContext.Provider>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `typeof window !== 'undefined'` check prevents bundling preloaded modules for SSR, optimizing server bundle size and build speed.
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
---
|
||||||
|
title: Deduplicate Global Event Listeners
|
||||||
|
impact: LOW
|
||||||
|
impactDescription: single listener for N components
|
||||||
|
tags: client, swr, event-listeners, subscription
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deduplicate Global Event Listeners
|
||||||
|
|
||||||
|
Use `useSWRSubscription()` to share global event listeners across component instances.
|
||||||
|
|
||||||
|
**Incorrect (N instances = N listeners):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function useKeyboardShortcut(key: string, callback: () => void) {
|
||||||
|
useEffect(() => {
|
||||||
|
const handler = (e: KeyboardEvent) => {
|
||||||
|
if (e.metaKey && e.key === key) {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.addEventListener('keydown', handler)
|
||||||
|
return () => window.removeEventListener('keydown', handler)
|
||||||
|
}, [key, callback])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When using the `useKeyboardShortcut` hook multiple times, each instance will register a new listener.
|
||||||
|
|
||||||
|
**Correct (N instances = 1 listener):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import useSWRSubscription from 'swr/subscription'
|
||||||
|
|
||||||
|
// Module-level Map to track callbacks per key
|
||||||
|
const keyCallbacks = new Map<string, Set<() => void>>()
|
||||||
|
|
||||||
|
function useKeyboardShortcut(key: string, callback: () => void) {
|
||||||
|
// Register this callback in the Map
|
||||||
|
useEffect(() => {
|
||||||
|
if (!keyCallbacks.has(key)) {
|
||||||
|
keyCallbacks.set(key, new Set())
|
||||||
|
}
|
||||||
|
keyCallbacks.get(key)!.add(callback)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const set = keyCallbacks.get(key)
|
||||||
|
if (set) {
|
||||||
|
set.delete(callback)
|
||||||
|
if (set.size === 0) {
|
||||||
|
keyCallbacks.delete(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [key, callback])
|
||||||
|
|
||||||
|
useSWRSubscription('global-keydown', () => {
|
||||||
|
const handler = (e: KeyboardEvent) => {
|
||||||
|
if (e.metaKey && keyCallbacks.has(e.key)) {
|
||||||
|
keyCallbacks.get(e.key)!.forEach(cb => cb())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.addEventListener('keydown', handler)
|
||||||
|
return () => window.removeEventListener('keydown', handler)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function Profile() {
|
||||||
|
// Multiple shortcuts will share the same listener
|
||||||
|
useKeyboardShortcut('p', () => { /* ... */ })
|
||||||
|
useKeyboardShortcut('k', () => { /* ... */ })
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
---
|
||||||
|
title: Version and Minimize localStorage Data
|
||||||
|
impact: MEDIUM
|
||||||
|
impactDescription: prevents schema conflicts, reduces storage size
|
||||||
|
tags: client, localStorage, storage, versioning, data-minimization
|
||||||
|
---
|
||||||
|
|
||||||
|
## Version and Minimize localStorage Data
|
||||||
|
|
||||||
|
Add version prefix to keys and store only needed fields. Prevents schema conflicts and accidental storage of sensitive data.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// No version, stores everything, no error handling
|
||||||
|
localStorage.setItem('userConfig', JSON.stringify(fullUserObject))
|
||||||
|
const data = localStorage.getItem('userConfig')
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const VERSION = 'v2'
|
||||||
|
|
||||||
|
function saveConfig(config: { theme: string; language: string }) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(`userConfig:${VERSION}`, JSON.stringify(config))
|
||||||
|
} catch {
|
||||||
|
// Throws in incognito/private browsing, quota exceeded, or disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadConfig() {
|
||||||
|
try {
|
||||||
|
const data = localStorage.getItem(`userConfig:${VERSION}`)
|
||||||
|
return data ? JSON.parse(data) : null
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migration from v1 to v2
|
||||||
|
function migrate() {
|
||||||
|
try {
|
||||||
|
const v1 = localStorage.getItem('userConfig:v1')
|
||||||
|
if (v1) {
|
||||||
|
const old = JSON.parse(v1)
|
||||||
|
saveConfig({ theme: old.darkMode ? 'dark' : 'light', language: old.lang })
|
||||||
|
localStorage.removeItem('userConfig:v1')
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Store minimal fields from server responses:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// User object has 20+ fields, only store what UI needs
|
||||||
|
function cachePrefs(user: FullUser) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem('prefs:v1', JSON.stringify({
|
||||||
|
theme: user.preferences.theme,
|
||||||
|
notifications: user.preferences.notifications
|
||||||
|
}))
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Always wrap in try-catch:** `getItem()` and `setItem()` throw in incognito/private browsing (Safari, Firefox), when quota exceeded, or when disabled.
|
||||||
|
|
||||||
|
**Benefits:** Schema evolution via versioning, reduced storage size, prevents storing tokens/PII/internal flags.
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
title: Use Passive Event Listeners for Scrolling Performance
|
||||||
|
impact: MEDIUM
|
||||||
|
impactDescription: eliminates scroll delay caused by event listeners
|
||||||
|
tags: client, event-listeners, scrolling, performance, touch, wheel
|
||||||
|
---
|
||||||
|
|
||||||
|
## Use Passive Event Listeners for Scrolling Performance
|
||||||
|
|
||||||
|
Add `{ passive: true }` to touch and wheel event listeners to enable immediate scrolling. Browsers normally wait for listeners to finish to check if `preventDefault()` is called, causing scroll delay.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
useEffect(() => {
|
||||||
|
const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)
|
||||||
|
const handleWheel = (e: WheelEvent) => console.log(e.deltaY)
|
||||||
|
|
||||||
|
document.addEventListener('touchstart', handleTouch)
|
||||||
|
document.addEventListener('wheel', handleWheel)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('touchstart', handleTouch)
|
||||||
|
document.removeEventListener('wheel', handleWheel)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
useEffect(() => {
|
||||||
|
const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)
|
||||||
|
const handleWheel = (e: WheelEvent) => console.log(e.deltaY)
|
||||||
|
|
||||||
|
document.addEventListener('touchstart', handleTouch, { passive: true })
|
||||||
|
document.addEventListener('wheel', handleWheel, { passive: true })
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('touchstart', handleTouch)
|
||||||
|
document.removeEventListener('wheel', handleWheel)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use passive when:** tracking/analytics, logging, any listener that doesn't call `preventDefault()`.
|
||||||
|
|
||||||
|
**Don't use passive when:** implementing custom swipe gestures, custom zoom controls, or any listener that needs `preventDefault()`.
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
title: Use SWR for Automatic Deduplication
|
||||||
|
impact: MEDIUM-HIGH
|
||||||
|
impactDescription: automatic deduplication
|
||||||
|
tags: client, swr, deduplication, data-fetching
|
||||||
|
---
|
||||||
|
|
||||||
|
## Use SWR for Automatic Deduplication
|
||||||
|
|
||||||
|
SWR enables request deduplication, caching, and revalidation across component instances.
|
||||||
|
|
||||||
|
**Incorrect (no deduplication, each instance fetches):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function UserList() {
|
||||||
|
const [users, setUsers] = useState([])
|
||||||
|
useEffect(() => {
|
||||||
|
fetch('/api/users')
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(setUsers)
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (multiple instances share one request):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import useSWR from 'swr'
|
||||||
|
|
||||||
|
function UserList() {
|
||||||
|
const { data: users } = useSWR('/api/users', fetcher)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**For immutable data:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useImmutableSWR } from '@/lib/swr'
|
||||||
|
|
||||||
|
function StaticContent() {
|
||||||
|
const { data } = useImmutableSWR('/api/config', fetcher)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**For mutations:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useSWRMutation } from 'swr/mutation'
|
||||||
|
|
||||||
|
function UpdateButton() {
|
||||||
|
const { trigger } = useSWRMutation('/api/user', updateUser)
|
||||||
|
return <button onClick={() => trigger()}>Update</button>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Reference: [https://swr.vercel.app](https://swr.vercel.app)
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
---
|
||||||
|
title: Avoid Layout Thrashing
|
||||||
|
impact: MEDIUM
|
||||||
|
impactDescription: prevents forced synchronous layouts and reduces performance bottlenecks
|
||||||
|
tags: javascript, dom, css, performance, reflow, layout-thrashing
|
||||||
|
---
|
||||||
|
|
||||||
|
## Avoid Layout Thrashing
|
||||||
|
|
||||||
|
Avoid interleaving style writes with layout reads. When you read a layout property (like `offsetWidth`, `getBoundingClientRect()`, or `getComputedStyle()`) between style changes, the browser is forced to trigger a synchronous reflow.
|
||||||
|
|
||||||
|
**This is OK (browser batches style changes):**
|
||||||
|
```typescript
|
||||||
|
function updateElementStyles(element: HTMLElement) {
|
||||||
|
// Each line invalidates style, but browser batches the recalculation
|
||||||
|
element.style.width = '100px'
|
||||||
|
element.style.height = '200px'
|
||||||
|
element.style.backgroundColor = 'blue'
|
||||||
|
element.style.border = '1px solid black'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Incorrect (interleaved reads and writes force reflows):**
|
||||||
|
```typescript
|
||||||
|
function layoutThrashing(element: HTMLElement) {
|
||||||
|
element.style.width = '100px'
|
||||||
|
const width = element.offsetWidth // Forces reflow
|
||||||
|
element.style.height = '200px'
|
||||||
|
const height = element.offsetHeight // Forces another reflow
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (batch writes, then read once):**
|
||||||
|
```typescript
|
||||||
|
function updateElementStyles(element: HTMLElement) {
|
||||||
|
// Batch all writes together
|
||||||
|
element.style.width = '100px'
|
||||||
|
element.style.height = '200px'
|
||||||
|
element.style.backgroundColor = 'blue'
|
||||||
|
element.style.border = '1px solid black'
|
||||||
|
|
||||||
|
// Read after all writes are done (single reflow)
|
||||||
|
const { width, height } = element.getBoundingClientRect()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (batch reads, then writes):**
|
||||||
|
```typescript
|
||||||
|
function avoidThrashing(element: HTMLElement) {
|
||||||
|
// Read phase - all layout queries first
|
||||||
|
const rect1 = element.getBoundingClientRect()
|
||||||
|
const offsetWidth = element.offsetWidth
|
||||||
|
const offsetHeight = element.offsetHeight
|
||||||
|
|
||||||
|
// Write phase - all style changes after
|
||||||
|
element.style.width = '100px'
|
||||||
|
element.style.height = '200px'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Better: use CSS classes**
|
||||||
|
```css
|
||||||
|
.highlighted-box {
|
||||||
|
width: 100px;
|
||||||
|
height: 200px;
|
||||||
|
background-color: blue;
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```typescript
|
||||||
|
function updateElementStyles(element: HTMLElement) {
|
||||||
|
element.classList.add('highlighted-box')
|
||||||
|
|
||||||
|
const { width, height } = element.getBoundingClientRect()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**React example:**
|
||||||
|
```tsx
|
||||||
|
// Incorrect: interleaving style changes with layout queries
|
||||||
|
function Box({ isHighlighted }: { isHighlighted: boolean }) {
|
||||||
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (ref.current && isHighlighted) {
|
||||||
|
ref.current.style.width = '100px'
|
||||||
|
const width = ref.current.offsetWidth // Forces layout
|
||||||
|
ref.current.style.height = '200px'
|
||||||
|
}
|
||||||
|
}, [isHighlighted])
|
||||||
|
|
||||||
|
return <div ref={ref}>Content</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Correct: toggle class
|
||||||
|
function Box({ isHighlighted }: { isHighlighted: boolean }) {
|
||||||
|
return (
|
||||||
|
<div className={isHighlighted ? 'highlighted-box' : ''}>
|
||||||
|
Content
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Prefer CSS classes over inline styles when possible. CSS files are cached by the browser, and classes provide better separation of concerns and are easier to maintain.
|
||||||
|
|
||||||
|
See [this gist](https://gist.github.com/paulirish/5d52fb081b3570c81e3a) and [CSS Triggers](https://csstriggers.com/) for more information on layout-forcing operations.
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
---
|
||||||
|
title: Cache Repeated Function Calls
|
||||||
|
impact: MEDIUM
|
||||||
|
impactDescription: avoid redundant computation
|
||||||
|
tags: javascript, cache, memoization, performance
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cache Repeated Function Calls
|
||||||
|
|
||||||
|
Use a module-level Map to cache function results when the same function is called repeatedly with the same inputs during render.
|
||||||
|
|
||||||
|
**Incorrect (redundant computation):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function ProjectList({ projects }: { projects: Project[] }) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{projects.map(project => {
|
||||||
|
// slugify() called 100+ times for same project names
|
||||||
|
const slug = slugify(project.name)
|
||||||
|
|
||||||
|
return <ProjectCard key={project.id} slug={slug} />
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (cached results):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Module-level cache
|
||||||
|
const slugifyCache = new Map<string, string>()
|
||||||
|
|
||||||
|
function cachedSlugify(text: string): string {
|
||||||
|
if (slugifyCache.has(text)) {
|
||||||
|
return slugifyCache.get(text)!
|
||||||
|
}
|
||||||
|
const result = slugify(text)
|
||||||
|
slugifyCache.set(text, result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function ProjectList({ projects }: { projects: Project[] }) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{projects.map(project => {
|
||||||
|
// Computed only once per unique project name
|
||||||
|
const slug = cachedSlugify(project.name)
|
||||||
|
|
||||||
|
return <ProjectCard key={project.id} slug={slug} />
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Simpler pattern for single-value functions:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
let isLoggedInCache: boolean | null = null
|
||||||
|
|
||||||
|
function isLoggedIn(): boolean {
|
||||||
|
if (isLoggedInCache !== null) {
|
||||||
|
return isLoggedInCache
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoggedInCache = document.cookie.includes('auth=')
|
||||||
|
return isLoggedInCache
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear cache when auth changes
|
||||||
|
function onAuthChange() {
|
||||||
|
isLoggedInCache = null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.
|
||||||
|
|
||||||
|
Reference: [How we made the Vercel Dashboard twice as fast](https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
title: Cache Property Access in Loops
|
||||||
|
impact: LOW-MEDIUM
|
||||||
|
impactDescription: reduces lookups
|
||||||
|
tags: javascript, loops, optimization, caching
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cache Property Access in Loops
|
||||||
|
|
||||||
|
Cache object property lookups in hot paths.
|
||||||
|
|
||||||
|
**Incorrect (3 lookups × N iterations):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
process(obj.config.settings.value)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (1 lookup total):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const value = obj.config.settings.value
|
||||||
|
const len = arr.length
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
process(value)
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
---
|
||||||
|
title: Cache Storage API Calls
|
||||||
|
impact: LOW-MEDIUM
|
||||||
|
impactDescription: reduces expensive I/O
|
||||||
|
tags: javascript, localStorage, storage, caching, performance
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cache Storage API Calls
|
||||||
|
|
||||||
|
`localStorage`, `sessionStorage`, and `document.cookie` are synchronous and expensive. Cache reads in memory.
|
||||||
|
|
||||||
|
**Incorrect (reads storage on every call):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function getTheme() {
|
||||||
|
return localStorage.getItem('theme') ?? 'light'
|
||||||
|
}
|
||||||
|
// Called 10 times = 10 storage reads
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (Map cache):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const storageCache = new Map<string, string | null>()
|
||||||
|
|
||||||
|
function getLocalStorage(key: string) {
|
||||||
|
if (!storageCache.has(key)) {
|
||||||
|
storageCache.set(key, localStorage.getItem(key))
|
||||||
|
}
|
||||||
|
return storageCache.get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLocalStorage(key: string, value: string) {
|
||||||
|
localStorage.setItem(key, value)
|
||||||
|
storageCache.set(key, value) // keep cache in sync
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.
|
||||||
|
|
||||||
|
**Cookie caching:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
let cookieCache: Record<string, string> | null = null
|
||||||
|
|
||||||
|
function getCookie(name: string) {
|
||||||
|
if (!cookieCache) {
|
||||||
|
cookieCache = Object.fromEntries(
|
||||||
|
document.cookie.split('; ').map(c => c.split('='))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return cookieCache[name]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important (invalidate on external changes):**
|
||||||
|
|
||||||
|
If storage can change externally (another tab, server-set cookies), invalidate cache:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
window.addEventListener('storage', (e) => {
|
||||||
|
if (e.key) storageCache.delete(e.key)
|
||||||
|
})
|
||||||
|
|
||||||
|
document.addEventListener('visibilitychange', () => {
|
||||||
|
if (document.visibilityState === 'visible') {
|
||||||
|
storageCache.clear()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
title: Combine Multiple Array Iterations
|
||||||
|
impact: LOW-MEDIUM
|
||||||
|
impactDescription: reduces iterations
|
||||||
|
tags: javascript, arrays, loops, performance
|
||||||
|
---
|
||||||
|
|
||||||
|
## Combine Multiple Array Iterations
|
||||||
|
|
||||||
|
Multiple `.filter()` or `.map()` calls iterate the array multiple times. Combine into one loop.
|
||||||
|
|
||||||
|
**Incorrect (3 iterations):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const admins = users.filter(u => u.isAdmin)
|
||||||
|
const testers = users.filter(u => u.isTester)
|
||||||
|
const inactive = users.filter(u => !u.isActive)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (1 iteration):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const admins: User[] = []
|
||||||
|
const testers: User[] = []
|
||||||
|
const inactive: User[] = []
|
||||||
|
|
||||||
|
for (const user of users) {
|
||||||
|
if (user.isAdmin) admins.push(user)
|
||||||
|
if (user.isTester) testers.push(user)
|
||||||
|
if (!user.isActive) inactive.push(user)
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
---
|
||||||
|
title: Early Return from Functions
|
||||||
|
impact: LOW-MEDIUM
|
||||||
|
impactDescription: avoids unnecessary computation
|
||||||
|
tags: javascript, functions, optimization, early-return
|
||||||
|
---
|
||||||
|
|
||||||
|
## Early Return from Functions
|
||||||
|
|
||||||
|
Return early when result is determined to skip unnecessary processing.
|
||||||
|
|
||||||
|
**Incorrect (processes all items even after finding answer):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function validateUsers(users: User[]) {
|
||||||
|
let hasError = false
|
||||||
|
let errorMessage = ''
|
||||||
|
|
||||||
|
for (const user of users) {
|
||||||
|
if (!user.email) {
|
||||||
|
hasError = true
|
||||||
|
errorMessage = 'Email required'
|
||||||
|
}
|
||||||
|
if (!user.name) {
|
||||||
|
hasError = true
|
||||||
|
errorMessage = 'Name required'
|
||||||
|
}
|
||||||
|
// Continues checking all users even after error found
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasError ? { valid: false, error: errorMessage } : { valid: true }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (returns immediately on first error):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function validateUsers(users: User[]) {
|
||||||
|
for (const user of users) {
|
||||||
|
if (!user.email) {
|
||||||
|
return { valid: false, error: 'Email required' }
|
||||||
|
}
|
||||||
|
if (!user.name) {
|
||||||
|
return { valid: false, error: 'Name required' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { valid: true }
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
title: Use flatMap to Map and Filter in One Pass
|
||||||
|
impact: LOW-MEDIUM
|
||||||
|
impactDescription: eliminates intermediate array
|
||||||
|
tags: javascript, arrays, flatMap, filter, performance
|
||||||
|
---
|
||||||
|
|
||||||
|
## Use flatMap to Map and Filter in One Pass
|
||||||
|
|
||||||
|
**Impact: LOW-MEDIUM (eliminates intermediate array)**
|
||||||
|
|
||||||
|
Chaining `.map().filter(Boolean)` creates an intermediate array and iterates twice. Use `.flatMap()` to transform and filter in a single pass.
|
||||||
|
|
||||||
|
**Incorrect (2 iterations, intermediate array):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const userNames = users
|
||||||
|
.map(user => user.isActive ? user.name : null)
|
||||||
|
.filter(Boolean)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (1 iteration, no intermediate array):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const userNames = users.flatMap(user =>
|
||||||
|
user.isActive ? [user.name] : []
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**More examples:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Extract valid emails from responses
|
||||||
|
// Before
|
||||||
|
const emails = responses
|
||||||
|
.map(r => r.success ? r.data.email : null)
|
||||||
|
.filter(Boolean)
|
||||||
|
|
||||||
|
// After
|
||||||
|
const emails = responses.flatMap(r =>
|
||||||
|
r.success ? [r.data.email] : []
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parse and filter valid numbers
|
||||||
|
// Before
|
||||||
|
const numbers = strings
|
||||||
|
.map(s => parseInt(s, 10))
|
||||||
|
.filter(n => !isNaN(n))
|
||||||
|
|
||||||
|
// After
|
||||||
|
const numbers = strings.flatMap(s => {
|
||||||
|
const n = parseInt(s, 10)
|
||||||
|
return isNaN(n) ? [] : [n]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**When to use:**
|
||||||
|
- Transforming items while filtering some out
|
||||||
|
- Conditional mapping where some inputs produce no output
|
||||||
|
- Parsing/validating where invalid inputs should be skipped
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
title: Hoist RegExp Creation
|
||||||
|
impact: LOW-MEDIUM
|
||||||
|
impactDescription: avoids recreation
|
||||||
|
tags: javascript, regexp, optimization, memoization
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hoist RegExp Creation
|
||||||
|
|
||||||
|
Don't create RegExp inside render. Hoist to module scope or memoize with `useMemo()`.
|
||||||
|
|
||||||
|
**Incorrect (new RegExp every render):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function Highlighter({ text, query }: Props) {
|
||||||
|
const regex = new RegExp(`(${query})`, 'gi')
|
||||||
|
const parts = text.split(regex)
|
||||||
|
return <>{parts.map((part, i) => ...)}</>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (memoize or hoist):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||||
|
|
||||||
|
function Highlighter({ text, query }: Props) {
|
||||||
|
const regex = useMemo(
|
||||||
|
() => new RegExp(`(${escapeRegex(query)})`, 'gi'),
|
||||||
|
[query]
|
||||||
|
)
|
||||||
|
const parts = text.split(regex)
|
||||||
|
return <>{parts.map((part, i) => ...)}</>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Warning (global regex has mutable state):**
|
||||||
|
|
||||||
|
Global regex (`/g`) has mutable `lastIndex` state:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const regex = /foo/g
|
||||||
|
regex.test('foo') // true, lastIndex = 3
|
||||||
|
regex.test('foo') // false, lastIndex = 0
|
||||||
|
```
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
title: Build Index Maps for Repeated Lookups
|
||||||
|
impact: LOW-MEDIUM
|
||||||
|
impactDescription: 1M ops to 2K ops
|
||||||
|
tags: javascript, map, indexing, optimization, performance
|
||||||
|
---
|
||||||
|
|
||||||
|
## Build Index Maps for Repeated Lookups
|
||||||
|
|
||||||
|
Multiple `.find()` calls by the same key should use a Map.
|
||||||
|
|
||||||
|
**Incorrect (O(n) per lookup):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function processOrders(orders: Order[], users: User[]) {
|
||||||
|
return orders.map(order => ({
|
||||||
|
...order,
|
||||||
|
user: users.find(u => u.id === order.userId)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (O(1) per lookup):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function processOrders(orders: Order[], users: User[]) {
|
||||||
|
const userById = new Map(users.map(u => [u.id, u]))
|
||||||
|
|
||||||
|
return orders.map(order => ({
|
||||||
|
...order,
|
||||||
|
user: userById.get(order.userId)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Build map once (O(n)), then all lookups are O(1).
|
||||||
|
For 1000 orders × 1000 users: 1M ops → 2K ops.
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
---
|
||||||
|
title: Early Length Check for Array Comparisons
|
||||||
|
impact: MEDIUM-HIGH
|
||||||
|
impactDescription: avoids expensive operations when lengths differ
|
||||||
|
tags: javascript, arrays, performance, optimization, comparison
|
||||||
|
---
|
||||||
|
|
||||||
|
## Early Length Check for Array Comparisons
|
||||||
|
|
||||||
|
When comparing arrays with expensive operations (sorting, deep equality, serialization), check lengths first. If lengths differ, the arrays cannot be equal.
|
||||||
|
|
||||||
|
In real-world applications, this optimization is especially valuable when the comparison runs in hot paths (event handlers, render loops).
|
||||||
|
|
||||||
|
**Incorrect (always runs expensive comparison):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function hasChanges(current: string[], original: string[]) {
|
||||||
|
// Always sorts and joins, even when lengths differ
|
||||||
|
return current.sort().join() !== original.sort().join()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Two O(n log n) sorts run even when `current.length` is 5 and `original.length` is 100. There is also overhead of joining the arrays and comparing the strings.
|
||||||
|
|
||||||
|
**Correct (O(1) length check first):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function hasChanges(current: string[], original: string[]) {
|
||||||
|
// Early return if lengths differ
|
||||||
|
if (current.length !== original.length) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Only sort when lengths match
|
||||||
|
const currentSorted = current.toSorted()
|
||||||
|
const originalSorted = original.toSorted()
|
||||||
|
for (let i = 0; i < currentSorted.length; i++) {
|
||||||
|
if (currentSorted[i] !== originalSorted[i]) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This new approach is more efficient because:
|
||||||
|
- It avoids the overhead of sorting and joining the arrays when lengths differ
|
||||||
|
- It avoids consuming memory for the joined strings (especially important for large arrays)
|
||||||
|
- It avoids mutating the original arrays
|
||||||
|
- It returns early when a difference is found
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
---
|
||||||
|
title: Use Loop for Min/Max Instead of Sort
|
||||||
|
impact: LOW
|
||||||
|
impactDescription: O(n) instead of O(n log n)
|
||||||
|
tags: javascript, arrays, performance, sorting, algorithms
|
||||||
|
---
|
||||||
|
|
||||||
|
## Use Loop for Min/Max Instead of Sort
|
||||||
|
|
||||||
|
Finding the smallest or largest element only requires a single pass through the array. Sorting is wasteful and slower.
|
||||||
|
|
||||||
|
**Incorrect (O(n log n) - sort to find latest):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface Project {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
updatedAt: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLatestProject(projects: Project[]) {
|
||||||
|
const sorted = [...projects].sort((a, b) => b.updatedAt - a.updatedAt)
|
||||||
|
return sorted[0]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Sorts the entire array just to find the maximum value.
|
||||||
|
|
||||||
|
**Incorrect (O(n log n) - sort for oldest and newest):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function getOldestAndNewest(projects: Project[]) {
|
||||||
|
const sorted = [...projects].sort((a, b) => a.updatedAt - b.updatedAt)
|
||||||
|
return { oldest: sorted[0], newest: sorted[sorted.length - 1] }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Still sorts unnecessarily when only min/max are needed.
|
||||||
|
|
||||||
|
**Correct (O(n) - single loop):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function getLatestProject(projects: Project[]) {
|
||||||
|
if (projects.length === 0) return null
|
||||||
|
|
||||||
|
let latest = projects[0]
|
||||||
|
|
||||||
|
for (let i = 1; i < projects.length; i++) {
|
||||||
|
if (projects[i].updatedAt > latest.updatedAt) {
|
||||||
|
latest = projects[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return latest
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOldestAndNewest(projects: Project[]) {
|
||||||
|
if (projects.length === 0) return { oldest: null, newest: null }
|
||||||
|
|
||||||
|
let oldest = projects[0]
|
||||||
|
let newest = projects[0]
|
||||||
|
|
||||||
|
for (let i = 1; i < projects.length; i++) {
|
||||||
|
if (projects[i].updatedAt < oldest.updatedAt) oldest = projects[i]
|
||||||
|
if (projects[i].updatedAt > newest.updatedAt) newest = projects[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return { oldest, newest }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Single pass through the array, no copying, no sorting.
|
||||||
|
|
||||||
|
**Alternative (Math.min/Math.max for small arrays):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const numbers = [5, 2, 8, 1, 9]
|
||||||
|
const min = Math.min(...numbers)
|
||||||
|
const max = Math.max(...numbers)
|
||||||
|
```
|
||||||
|
|
||||||
|
This works for small arrays, but can be slower or just throw an error for very large arrays due to spread operator limitations. Maximal array length is approximately 124000 in Chrome 143 and 638000 in Safari 18; exact numbers may vary - see [the fiddle](https://jsfiddle.net/qw1jabsx/4/). Use the loop approach for reliability.
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
title: Use Set/Map for O(1) Lookups
|
||||||
|
impact: LOW-MEDIUM
|
||||||
|
impactDescription: O(n) to O(1)
|
||||||
|
tags: javascript, set, map, data-structures, performance
|
||||||
|
---
|
||||||
|
|
||||||
|
## Use Set/Map for O(1) Lookups
|
||||||
|
|
||||||
|
Convert arrays to Set/Map for repeated membership checks.
|
||||||
|
|
||||||
|
**Incorrect (O(n) per check):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const allowedIds = ['a', 'b', 'c', ...]
|
||||||
|
items.filter(item => allowedIds.includes(item.id))
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (O(1) per check):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const allowedIds = new Set(['a', 'b', 'c', ...])
|
||||||
|
items.filter(item => allowedIds.has(item.id))
|
||||||
|
```
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
---
|
||||||
|
title: Use toSorted() Instead of sort() for Immutability
|
||||||
|
impact: MEDIUM-HIGH
|
||||||
|
impactDescription: prevents mutation bugs in React state
|
||||||
|
tags: javascript, arrays, immutability, react, state, mutation
|
||||||
|
---
|
||||||
|
|
||||||
|
## Use toSorted() Instead of sort() for Immutability
|
||||||
|
|
||||||
|
`.sort()` mutates the array in place, which can cause bugs with React state and props. Use `.toSorted()` to create a new sorted array without mutation.
|
||||||
|
|
||||||
|
**Incorrect (mutates original array):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function UserList({ users }: { users: User[] }) {
|
||||||
|
// Mutates the users prop array!
|
||||||
|
const sorted = useMemo(
|
||||||
|
() => users.sort((a, b) => a.name.localeCompare(b.name)),
|
||||||
|
[users]
|
||||||
|
)
|
||||||
|
return <div>{sorted.map(renderUser)}</div>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (creates new array):**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function UserList({ users }: { users: User[] }) {
|
||||||
|
// Creates new sorted array, original unchanged
|
||||||
|
const sorted = useMemo(
|
||||||
|
() => users.toSorted((a, b) => a.name.localeCompare(b.name)),
|
||||||
|
[users]
|
||||||
|
)
|
||||||
|
return <div>{sorted.map(renderUser)}</div>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why this matters in React:**
|
||||||
|
|
||||||
|
1. Props/state mutations break React's immutability model - React expects props and state to be treated as read-only
|
||||||
|
2. Causes stale closure bugs - Mutating arrays inside closures (callbacks, effects) can lead to unexpected behavior
|
||||||
|
|
||||||
|
**Browser support (fallback for older browsers):**
|
||||||
|
|
||||||
|
`.toSorted()` is available in all modern browsers (Chrome 110+, Safari 16+, Firefox 115+, Node.js 20+). For older environments, use spread operator:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Fallback for older browsers
|
||||||
|
const sorted = [...items].sort((a, b) => a.value - b.value)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Other immutable array methods:**
|
||||||
|
|
||||||
|
- `.toSorted()` - immutable sort
|
||||||
|
- `.toReversed()` - immutable reverse
|
||||||
|
- `.toSpliced()` - immutable splice
|
||||||
|
- `.with()` - immutable element replacement
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
title: Use Activity Component for Show/Hide
|
||||||
|
impact: MEDIUM
|
||||||
|
impactDescription: preserves state/DOM
|
||||||
|
tags: rendering, activity, visibility, state-preservation
|
||||||
|
---
|
||||||
|
|
||||||
|
## Use Activity Component for Show/Hide
|
||||||
|
|
||||||
|
Use React's `<Activity>` to preserve state/DOM for expensive components that frequently toggle visibility.
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Activity } from 'react'
|
||||||
|
|
||||||
|
function Dropdown({ isOpen }: Props) {
|
||||||
|
return (
|
||||||
|
<Activity mode={isOpen ? 'visible' : 'hidden'}>
|
||||||
|
<ExpensiveMenu />
|
||||||
|
</Activity>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Avoids expensive re-renders and state loss.
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
---
|
||||||
|
title: Animate SVG Wrapper Instead of SVG Element
|
||||||
|
impact: LOW
|
||||||
|
impactDescription: enables hardware acceleration
|
||||||
|
tags: rendering, svg, css, animation, performance
|
||||||
|
---
|
||||||
|
|
||||||
|
## Animate SVG Wrapper Instead of SVG Element
|
||||||
|
|
||||||
|
Many browsers don't have hardware acceleration for CSS3 animations on SVG elements. Wrap SVG in a `<div>` and animate the wrapper instead.
|
||||||
|
|
||||||
|
**Incorrect (animating SVG directly - no hardware acceleration):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function LoadingSpinner() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="animate-spin"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="10" stroke="currentColor" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (animating wrapper div - hardware accelerated):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function LoadingSpinner() {
|
||||||
|
return (
|
||||||
|
<div className="animate-spin">
|
||||||
|
<svg
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="10" stroke="currentColor" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This applies to all CSS transforms and transitions (`transform`, `opacity`, `translate`, `scale`, `rotate`). The wrapper div allows browsers to use GPU acceleration for smoother animations.
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
title: Use Explicit Conditional Rendering
|
||||||
|
impact: LOW
|
||||||
|
impactDescription: prevents rendering 0 or NaN
|
||||||
|
tags: rendering, conditional, jsx, falsy-values
|
||||||
|
---
|
||||||
|
|
||||||
|
## Use Explicit Conditional Rendering
|
||||||
|
|
||||||
|
Use explicit ternary operators (`? :`) instead of `&&` for conditional rendering when the condition can be `0`, `NaN`, or other falsy values that render.
|
||||||
|
|
||||||
|
**Incorrect (renders "0" when count is 0):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function Badge({ count }: { count: number }) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{count && <span className="badge">{count}</span>}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// When count = 0, renders: <div>0</div>
|
||||||
|
// When count = 5, renders: <div><span class="badge">5</span></div>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (renders nothing when count is 0):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function Badge({ count }: { count: number }) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{count > 0 ? <span className="badge">{count}</span> : null}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// When count = 0, renders: <div></div>
|
||||||
|
// When count = 5, renders: <div><span class="badge">5</span></div>
|
||||||
|
```
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
title: CSS content-visibility for Long Lists
|
||||||
|
impact: HIGH
|
||||||
|
impactDescription: faster initial render
|
||||||
|
tags: rendering, css, content-visibility, long-lists
|
||||||
|
---
|
||||||
|
|
||||||
|
## CSS content-visibility for Long Lists
|
||||||
|
|
||||||
|
Apply `content-visibility: auto` to defer off-screen rendering.
|
||||||
|
|
||||||
|
**CSS:**
|
||||||
|
|
||||||
|
```css
|
||||||
|
.message-item {
|
||||||
|
content-visibility: auto;
|
||||||
|
contain-intrinsic-size: 0 80px;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function MessageList({ messages }: { messages: Message[] }) {
|
||||||
|
return (
|
||||||
|
<div className="overflow-y-auto h-screen">
|
||||||
|
{messages.map(msg => (
|
||||||
|
<div key={msg.id} className="message-item">
|
||||||
|
<Avatar user={msg.author} />
|
||||||
|
<div>{msg.content}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For 1000 messages, browser skips layout/paint for ~990 off-screen items (10× faster initial render).
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
title: Hoist Static JSX Elements
|
||||||
|
impact: LOW
|
||||||
|
impactDescription: avoids re-creation
|
||||||
|
tags: rendering, jsx, static, optimization
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hoist Static JSX Elements
|
||||||
|
|
||||||
|
Extract static JSX outside components to avoid re-creation.
|
||||||
|
|
||||||
|
**Incorrect (recreates element every render):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function LoadingSkeleton() {
|
||||||
|
return <div className="animate-pulse h-20 bg-gray-200" />
|
||||||
|
}
|
||||||
|
|
||||||
|
function Container() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{loading && <LoadingSkeleton />}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (reuses same element):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const loadingSkeleton = (
|
||||||
|
<div className="animate-pulse h-20 bg-gray-200" />
|
||||||
|
)
|
||||||
|
|
||||||
|
function Container() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{loading && loadingSkeleton}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This is especially helpful for large and static SVG nodes, which can be expensive to recreate on every render.
|
||||||
|
|
||||||
|
**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler automatically hoists static JSX elements and optimizes component re-renders, making manual hoisting unnecessary.
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
---
|
||||||
|
title: Prevent Hydration Mismatch Without Flickering
|
||||||
|
impact: MEDIUM
|
||||||
|
impactDescription: avoids visual flicker and hydration errors
|
||||||
|
tags: rendering, ssr, hydration, localStorage, flicker
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prevent Hydration Mismatch Without Flickering
|
||||||
|
|
||||||
|
When rendering content that depends on client-side storage (localStorage, cookies), avoid both SSR breakage and post-hydration flickering by injecting a synchronous script that updates the DOM before React hydrates.
|
||||||
|
|
||||||
|
**Incorrect (breaks SSR):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function ThemeWrapper({ children }: { children: ReactNode }) {
|
||||||
|
// localStorage is not available on server - throws error
|
||||||
|
const theme = localStorage.getItem('theme') || 'light'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={theme}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Server-side rendering will fail because `localStorage` is undefined.
|
||||||
|
|
||||||
|
**Incorrect (visual flickering):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function ThemeWrapper({ children }: { children: ReactNode }) {
|
||||||
|
const [theme, setTheme] = useState('light')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Runs after hydration - causes visible flash
|
||||||
|
const stored = localStorage.getItem('theme')
|
||||||
|
if (stored) {
|
||||||
|
setTheme(stored)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={theme}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Component first renders with default value (`light`), then updates after hydration, causing a visible flash of incorrect content.
|
||||||
|
|
||||||
|
**Correct (no flicker, no hydration mismatch):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
function ThemeWrapper({ children }: { children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div id="theme-wrapper">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
<script
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `
|
||||||
|
(function() {
|
||||||
|
try {
|
||||||
|
var theme = localStorage.getItem('theme') || 'light';
|
||||||
|
var el = document.getElementById('theme-wrapper');
|
||||||
|
if (el) el.className = theme;
|
||||||
|
} catch (e) {}
|
||||||
|
})();
|
||||||
|
`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The inline script executes synchronously before showing the element, ensuring the DOM already has the correct value. No flickering, no hydration mismatch.
|
||||||
|
|
||||||
|
This pattern is especially useful for theme toggles, user preferences, authentication states, and any client-only data that should render immediately without flashing default values.
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user