Add web-player Claude Code skills

Installed skills for the SvelteKit/Tailwind/Vite stack under
web-player/.agents/skills (source of truth) with web-player/.claude/skills
symlinked to it for tool compatibility, plus skills-lock.json tracking
each skill's registry source and content hash.
This commit is contained in:
2026-09-09 23:18:42 +02:00
parent b5700614c1
commit 5658dd24ff
47 changed files with 9406 additions and 0 deletions
@@ -0,0 +1,440 @@
---
name: accessibility
description: Audit and improve web accessibility following WCAG 2.2 guidelines. Use when asked to "improve accessibility", "a11y audit", "WCAG compliance", "screen reader support", "keyboard navigation", or "make accessible".
license: MIT
metadata:
author: web-quality-skills
version: "1.1"
---
# Accessibility (a11y)
Comprehensive accessibility guidelines based on WCAG 2.2 and Lighthouse accessibility audits. Goal: make content usable by everyone, including people with disabilities.
## WCAG Principles: POUR
| Principle | Description |
|-----------|-------------|
| **P**erceivable | Content can be perceived through different senses |
| **O**perable | Interface can be operated by all users |
| **U**nderstandable | Content and interface are understandable |
| **R**obust | Content works with assistive technologies |
## Conformance levels
| Level | Requirement | Target |
|-------|-------------|--------|
| **A** | Minimum accessibility | Must pass |
| **AA** | Standard compliance | Should pass (legal requirement in many jurisdictions) |
| **AAA** | Enhanced accessibility | Nice to have |
---
## Perceivable
### Text alternatives (1.1)
**Images require alt text:**
```html
<!-- ❌ Missing alt -->
<img src="chart.png">
<!-- ✅ Descriptive alt -->
<img src="chart.png" alt="Bar chart showing 40% increase in Q3 sales">
<!-- ✅ Decorative image (empty alt) -->
<img src="decorative-border.png" alt="" role="presentation">
<!-- ✅ Complex image with longer description -->
<figure>
<img src="infographic.png" alt="2024 market trends infographic"
aria-describedby="infographic-desc">
<figcaption id="infographic-desc">
<!-- Detailed description -->
</figcaption>
</figure>
```
**Icon buttons need accessible names:**
```html
<!-- ❌ No accessible name -->
<button><svg><!-- menu icon --></svg></button>
<!-- ✅ Using aria-label -->
<button aria-label="Open menu">
<svg aria-hidden="true"><!-- menu icon --></svg>
</button>
<!-- ✅ Using visually hidden text -->
<button>
<svg aria-hidden="true"><!-- menu icon --></svg>
<span class="visually-hidden">Open menu</span>
</button>
```
**Visually hidden class:**
```css
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
```
### Color contrast (1.4.3, 1.4.6)
| Text Size | AA minimum | AAA enhanced |
|-----------|------------|--------------|
| Normal text (< 18px / < 14px bold) | 4.5:1 | 7:1 |
| Large text (≥ 18px / ≥ 14px bold) | 3:1 | 4.5:1 |
| UI components & graphics | 3:1 | 3:1 |
```css
/* ❌ Low contrast (2.5:1) */
.low-contrast {
color: #999;
background: #fff;
}
/* ✅ Sufficient contrast (7:1) */
.high-contrast {
color: #333;
background: #fff;
}
/* ✅ Focus states need contrast too */
:focus-visible {
outline: 2px solid #005fcc;
outline-offset: 2px;
}
```
**Don't rely on color alone:**
```html
<!-- ❌ Only color indicates error -->
<input class="error-border">
<style>.error-border { border-color: red; }</style>
<!-- ✅ Color + icon + text -->
<div class="field-error">
<input aria-invalid="true" aria-describedby="email-error">
<span id="email-error" class="error-message">
<svg aria-hidden="true"><!-- error icon --></svg>
Please enter a valid email address
</span>
</div>
```
### Media alternatives (1.2)
```html
<!-- Video with captions -->
<video controls>
<source src="video.mp4" type="video/mp4">
<track kind="captions" src="captions.vtt" srclang="en" label="English" default>
<track kind="descriptions" src="descriptions.vtt" srclang="en" label="Descriptions">
</video>
<!-- Audio with transcript -->
<audio controls>
<source src="podcast.mp3" type="audio/mp3">
</audio>
<details>
<summary>Transcript</summary>
<p>Full transcript text...</p>
</details>
```
---
## Operable
### Keyboard accessible (2.1)
**All functionality must be keyboard accessible:**
```javascript
// ❌ Only handles click
element.addEventListener('click', handleAction);
// ✅ Handles both click and keyboard
element.addEventListener('click', handleAction);
element.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleAction();
}
});
```
**No keyboard traps.** Users must be able to Tab into and out of every component. Use the [modal focus trap pattern](references/A11Y-PATTERNS.md#modal-focus-trap) for dialogs—the native `<dialog>` element handles this automatically.
### Focus visible (2.4.7)
```css
/* ❌ Never remove focus outlines */
*:focus { outline: none; }
/* ✅ Use :focus-visible for keyboard-only focus */
:focus {
outline: none;
}
:focus-visible {
outline: 2px solid #005fcc;
outline-offset: 2px;
}
/* ✅ Or custom focus styles */
button:focus-visible {
box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.5);
}
```
### Focus not obscured (2.4.11) — new in 2.2
When an element receives keyboard focus, it must not be entirely hidden by other author-created content such as sticky headers, footers, or overlapping panels. At Level AAA (2.4.12), no part of the focused element may be hidden.
```css
/* ✅ Account for sticky headers when scrolling to focused elements */
:target {
scroll-margin-top: 80px;
}
/* ✅ Ensure focused items clear fixed/sticky bars */
:focus {
scroll-margin-top: 80px;
scroll-margin-bottom: 60px;
}
```
### Skip links (2.4.1)
Provide a skip link so keyboard users can bypass repetitive navigation. See the [skip link pattern](references/A11Y-PATTERNS.md#skip-link) for full markup and styles.
### Target size (2.5.8) — new in 2.2
Interactive targets must be at least **24 × 24 CSS pixels** (AA). Exceptions: inline text links, elements where the browser controls the size, and targets where a 24px circle centered on the bounding box does not overlap another target.
```css
/* ✅ Minimum target size */
button,
[role="button"],
input[type="checkbox"] + label,
input[type="radio"] + label {
min-width: 24px;
min-height: 24px;
}
/* ✅ Comfortable target size (recommended 44×44) */
.touch-target {
min-width: 44px;
min-height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
}
```
### Dragging movements (2.5.7) — new in 2.2
Any action that requires dragging must have a single-pointer alternative (e.g., buttons, inputs). See the [dragging movements pattern](references/A11Y-PATTERNS.md#dragging-movements) for a sortable-list example.
### Timing (2.2)
```javascript
// Allow users to extend time limits
function showSessionWarning() {
const modal = createModal({
title: 'Session Expiring',
content: 'Your session will expire in 2 minutes.',
actions: [
{ label: 'Extend session', action: extendSession },
{ label: 'Log out', action: logout }
],
timeout: 120000
});
}
```
### Motion (2.3)
```css
/* Respect reduced motion preference */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
```
---
## Understandable
### Page language (3.1.1)
```html
<!-- ❌ No language specified -->
<html>
<!-- ✅ Language specified -->
<html lang="en">
<!-- ✅ Language changes within page -->
<p>The French word for hello is <span lang="fr">bonjour</span>.</p>
```
### Consistent navigation (3.2.3)
```html
<!-- Navigation should be consistent across pages -->
<nav aria-label="Main">
<ul>
<li><a href="/" aria-current="page">Home</a></li>
<li><a href="/products">Products</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
```
### Consistent help (3.2.6) — new in 2.2
If a help mechanism (contact info, chat widget, FAQ link, self-help option) is repeated across multiple pages, it must appear in the **same relative order** each time. Users who rely on consistent placement shouldn't have to hunt for help on every page.
### Form labels (3.3.2)
Every input needs a programmatically associated label. See the [form labels pattern](references/A11Y-PATTERNS.md#form-labels) for explicit, implicit, and instructional examples.
### Error handling (3.3.1, 3.3.3)
Announce errors to screen readers with `role="alert"` or `aria-live`, set `aria-invalid="true"` on invalid fields, and focus the first error on submit. See the [error handling pattern](references/A11Y-PATTERNS.md#error-handling) for full markup and JS.
### Redundant entry (3.3.7) — new in 2.2
Don't force users to re-enter information they already provided in the same session. Auto-populate from earlier steps, or let users select from previously entered values. Exceptions: security re-confirmation and content that has expired.
```html
<!-- ✅ Auto-fill shipping address from billing -->
<fieldset>
<legend>Shipping address</legend>
<label>
<input type="checkbox" id="same-as-billing" checked>
Same as billing address
</label>
<!-- Fields auto-populated when checked -->
</fieldset>
```
### Accessible authentication (3.3.8) — new in 2.2
Login flows must not rely on cognitive function tests (e.g., remembering a password, solving a puzzle) unless at least one of:
- A copy-paste or autofill mechanism is available
- An alternative method exists (e.g., passkey, SSO, email link)
- The test uses object recognition or personal content (AA only; AAA removes this exception)
```html
<!-- ✅ Allow paste in password fields -->
<input type="password" id="password" autocomplete="current-password">
<!-- ✅ Offer passwordless alternatives -->
<button type="button">Sign in with passkey</button>
<button type="button">Email me a login link</button>
```
---
## Robust
### ARIA usage (4.1.2)
**Prefer native elements:**
```html
<!-- ❌ ARIA role on div -->
<div role="button" tabindex="0">Click me</div>
<!-- ✅ Native button -->
<button>Click me</button>
<!-- ❌ ARIA checkbox -->
<div role="checkbox" aria-checked="false">Option</div>
<!-- ✅ Native checkbox -->
<label><input type="checkbox"> Option</label>
```
**When ARIA is needed,** use the correct roles and states. See the [ARIA tabs pattern](references/A11Y-PATTERNS.md#aria-tabs) for a complete tablist example.
### Live regions (4.1.3)
Use `aria-live` regions to announce dynamic content changes without moving focus. See the [live regions pattern](references/A11Y-PATTERNS.md#live-regions-and-notifications) for markup and a `showNotification()` helper.
---
## Testing checklist
### Automated testing
```bash
# Lighthouse accessibility audit
npx lighthouse https://example.com --only-categories=accessibility
# axe-core
npm install @axe-core/cli -g
axe https://example.com
```
### Manual testing
- [ ] **Keyboard navigation:** Tab through entire page, use Enter/Space to activate
- [ ] **Screen reader:** Test with VoiceOver (Mac), NVDA (Windows), or TalkBack (Android)
- [ ] **Zoom:** Content usable at 200% zoom
- [ ] **High contrast:** Test with Windows High Contrast Mode
- [ ] **Reduced motion:** Test with `prefers-reduced-motion: reduce`
- [ ] **Focus order:** Logical and follows visual order
- [ ] **Target size:** Interactive elements meet 24×24px minimum
See the [screen reader commands reference](references/A11Y-PATTERNS.md#screen-reader-commands) for VoiceOver and NVDA shortcuts.
---
## Common issues by impact
### Critical (fix immediately)
1. Missing form labels
2. Missing image alt text
3. Insufficient color contrast
4. Keyboard traps
5. No focus indicators
### Serious (fix before launch)
1. Missing page language
2. Missing heading structure
3. Non-descriptive link text
4. Auto-playing media
5. Missing skip links
### Moderate (fix soon)
1. Missing ARIA labels on icons
2. Inconsistent navigation
3. Missing error identification
4. Timing without controls
5. Missing landmark regions
## References
- [WCAG 2.2 Quick Reference](https://www.w3.org/WAI/WCAG22/quickref/)
- [WAI-ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/)
- [Deque axe Rules](https://dequeuniversity.com/rules/axe/)
- [Web Quality Audit](../web-quality-audit/SKILL.md)
- [WCAG criteria reference](references/WCAG.md)
- [Accessibility code patterns](references/A11Y-PATTERNS.md)
@@ -0,0 +1,233 @@
# Accessibility Code Patterns
Practical, copy-paste-ready patterns for common accessibility requirements. Each pattern is self-contained and linked from the main [SKILL.md](../SKILL.md).
---
## Modal focus trap
Trap keyboard focus inside a modal dialog so Tab/Shift+Tab cycle through its focusable elements and Escape closes it.
```javascript
function openModal(modal) {
const focusableElements = modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
modal.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
if (e.shiftKey && document.activeElement === firstElement) {
e.preventDefault();
lastElement.focus();
} else if (!e.shiftKey && document.activeElement === lastElement) {
e.preventDefault();
firstElement.focus();
}
}
if (e.key === 'Escape') {
closeModal();
}
});
firstElement.focus();
}
```
The native `<dialog>` element handles focus trapping automatically—prefer it when browser support allows.
---
## Skip link
Allows keyboard users to bypass repetitive navigation and jump straight to main content.
```html
<body>
<a href="#main-content" class="skip-link">Skip to main content</a>
<header><!-- navigation --></header>
<main id="main-content" tabindex="-1">
<!-- main content -->
</main>
</body>
```
```css
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: #fff;
padding: 8px 16px;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
```
---
## Error handling
Announce errors to screen readers and focus the first invalid field on submit.
```html
<form novalidate>
<div class="field" aria-live="polite">
<label for="email">Email</label>
<input type="email" id="email"
aria-invalid="true"
aria-describedby="email-error">
<p id="email-error" class="error" role="alert">
Please enter a valid email address (e.g., name@example.com)
</p>
</div>
</form>
```
```javascript
form.addEventListener('submit', (e) => {
const firstError = form.querySelector('[aria-invalid="true"]');
if (firstError) {
e.preventDefault();
firstError.focus();
const errorSummary = document.getElementById('error-summary');
errorSummary.textContent =
`${errors.length} errors found. Please fix them and try again.`;
errorSummary.focus();
}
});
```
---
## Form labels
Every input needs an associated label—either explicit (`for`/`id`) or implicit (wrapping `<label>`).
```html
<!-- ❌ No label association -->
<input type="email" placeholder="Email">
<!-- ✅ Explicit label -->
<label for="email">Email address</label>
<input type="email" id="email" name="email"
autocomplete="email" required>
<!-- ✅ Implicit label -->
<label>
Email address
<input type="email" name="email" autocomplete="email" required>
</label>
<!-- ✅ With instructions -->
<label for="password">Password</label>
<input type="password" id="password"
aria-describedby="password-requirements">
<p id="password-requirements">
Must be at least 8 characters with one number.
</p>
```
---
## Dragging movements
Any action triggered by dragging must offer a single-pointer alternative (WCAG 2.5.7).
```html
<!-- ❌ Drag-only reorder -->
<ul class="sortable-list" draggable="true">
<li>Item 1</li>
<li>Item 2</li>
</ul>
<!-- ✅ Drag + button alternatives -->
<ul class="sortable-list">
<li>
<span>Item 1</span>
<button aria-label="Move Item 1 up">↑</button>
<button aria-label="Move Item 1 down">↓</button>
</li>
<li>
<span>Item 2</span>
<button aria-label="Move Item 2 up">↑</button>
<button aria-label="Move Item 2 down">↓</button>
</li>
</ul>
```
Also applies to sliders, map panning, colour pickers, and similar drag-based widgets—always provide an equivalent click/tap or keyboard path.
---
## ARIA tabs
Tabs require `role="tablist"`, `role="tab"`, and `role="tabpanel"` with proper `aria-selected`, `aria-controls`, and keyboard support.
```html
<div role="tablist" aria-label="Product information">
<button role="tab" id="tab-1" aria-selected="true"
aria-controls="panel-1">Description</button>
<button role="tab" id="tab-2" aria-selected="false"
aria-controls="panel-2" tabindex="-1">Reviews</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
<!-- Panel content -->
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
<!-- Panel content -->
</div>
```
Arrow keys should move focus between tabs; the active tab receives `tabindex="0"` while inactive tabs use `tabindex="-1"`.
---
## Live regions and notifications
Use `aria-live` to announce dynamic content changes to screen readers without moving focus.
```html
<!-- Status updates (polite — waits for pause in speech) -->
<div aria-live="polite" aria-atomic="true" class="status">
<!-- Content updates announced to screen readers -->
</div>
<!-- Urgent alerts (assertive — interrupts) -->
<div role="alert" aria-live="assertive">
<!-- Interrupts current announcement -->
</div>
```
```javascript
function showNotification(message, type = 'polite') {
const container = document.getElementById(`${type}-announcer`);
container.textContent = '';
requestAnimationFrame(() => {
container.textContent = message;
});
}
```
Clear the container before writing to ensure the same message triggers a new announcement.
---
## Screen reader commands
Quick reference for the most common screen reader shortcuts.
| Action | VoiceOver (Mac) | NVDA (Windows) |
|--------|-----------------|----------------|
| Start/Stop | ⌘ + F5 | Ctrl + Alt + N |
| Next item | VO + → | ↓ |
| Previous item | VO + ← | ↑ |
| Activate | VO + Space | Enter |
| Headings list | VO + U, then arrows | H / Shift + H |
| Links list | VO + U | K / Shift + K |
@@ -0,0 +1,191 @@
# WCAG 2.2 Quick Reference
## Success criteria by level
### Level A (minimum)
| Criterion | Description |
|-----------|-------------|
| **1.1.1** Non-text Content | All images, icons have text alternatives |
| **1.2.1** Audio-only/Video-only | Provide transcript or audio description |
| **1.2.2** Captions | Video with audio has captions |
| **1.2.3** Audio Description | Video has audio description |
| **1.3.1** Info and Relationships | Information conveyed through presentation is available programmatically |
| **1.3.2** Meaningful Sequence | Reading order is logical |
| **1.3.3** Sensory Characteristics | Instructions don't rely solely on shape, color, size, location, orientation, or sound |
| **1.4.1** Use of Color | Color is not the only visual means of conveying information |
| **1.4.2** Audio Control | Audio playing automatically can be paused/stopped |
| **2.1.1** Keyboard | All functionality available via keyboard |
| **2.1.2** No Keyboard Trap | Keyboard focus can be moved away from any component |
| **2.1.4** Character Key Shortcuts | Single-key shortcuts can be turned off or remapped |
| **2.2.1** Timing Adjustable | Time limits can be extended |
| **2.2.2** Pause, Stop, Hide | Moving/blinking content can be paused |
| **2.3.1** Three Flashes | Nothing flashes more than 3 times per second |
| **2.4.1** Bypass Blocks | Skip link or landmark navigation available |
| **2.4.2** Page Titled | Pages have descriptive titles |
| **2.4.3** Focus Order | Focus order preserves meaning |
| **2.4.4** Link Purpose | Link purpose clear from link text or context |
| **2.5.1** Pointer Gestures | Multi-point gestures have single-pointer alternatives |
| **2.5.2** Pointer Cancellation | Down-event doesn't trigger action (use up-event or click) |
| **2.5.3** Label in Name | Accessible name contains visible label text |
| **2.5.4** Motion Actuation | Motion-triggered functions have alternatives |
| **3.1.1** Language of Page | Default language specified in HTML |
| **3.2.1** On Focus | Focus doesn't trigger unexpected changes |
| **3.2.2** On Input | Input doesn't trigger unexpected changes |
| **3.2.6** Consistent Help | Help mechanisms appear in the same relative order across pages |
| **3.3.1** Error Identification | Input errors clearly described |
| **3.3.2** Labels or Instructions | Form inputs have labels or instructions |
| **3.3.7** Redundant Entry | Information previously entered is auto-populated or available to select |
| **4.1.2** Name, Role, Value | UI components have accessible names and correct roles |
### Level AA (standard)
| Criterion | Description |
|-----------|-------------|
| **1.2.4** Captions (Live) | Live audio has captions |
| **1.2.5** Audio Description | Pre-recorded video has audio description |
| **1.3.4** Orientation | Content doesn't restrict orientation |
| **1.3.5** Identify Input Purpose | Input purpose can be programmatically determined |
| **1.4.3** Contrast (Minimum) | 4.5:1 for normal text, 3:1 for large text |
| **1.4.4** Resize Text | Text can be resized to 200% without loss of functionality |
| **1.4.5** Images of Text | Text used instead of images of text |
| **1.4.10** Reflow | Content reflows at 320px width without horizontal scroll |
| **1.4.11** Non-text Contrast | UI components have 3:1 contrast |
| **1.4.12** Text Spacing | Content adapts to text spacing changes |
| **1.4.13** Content on Hover/Focus | Additional content is dismissible, hoverable, persistent |
| **2.4.5** Multiple Ways | Multiple ways to find pages |
| **2.4.6** Headings and Labels | Headings and labels are descriptive |
| **2.4.7** Focus Visible | Focus indicator is visible |
| **2.4.11** Focus Not Obscured (Minimum) | Focused element is not entirely hidden by author-created content |
| **2.5.7** Dragging Movements | Dragging actions have single-pointer alternatives |
| **2.5.8** Target Size (Minimum) | Interactive targets are at least 24×24 CSS pixels (with exceptions) |
| **3.1.2** Language of Parts | Language changes are marked |
| **3.2.3** Consistent Navigation | Navigation is consistent across pages |
| **3.2.4** Consistent Identification | Same functionality uses same labels |
| **3.3.3** Error Suggestion | Error corrections suggested when known |
| **3.3.4** Error Prevention (Legal) | Actions can be reversed or confirmed |
| **3.3.8** Accessible Authentication (Minimum) | No cognitive function test for login unless an alternative or assistance is provided |
| **4.1.3** Status Messages | Status messages announced to screen readers |
### Level AAA (enhanced)
| Criterion | Description |
|-----------|-------------|
| **1.4.6** Contrast (Enhanced) | 7:1 for normal text, 4.5:1 for large text |
| **1.4.8** Visual Presentation | Foreground/background colors can be selected |
| **1.4.9** Images of Text (No Exception) | No images of text |
| **2.1.3** Keyboard (No Exception) | All functionality keyboard accessible |
| **2.2.3** No Timing | No time limits |
| **2.2.4** Interruptions | Interruptions can be postponed |
| **2.2.5** Re-authenticating | Data preserved on re-authentication |
| **2.2.6** Timeouts | Users warned about data loss from inactivity |
| **2.3.2** Three Flashes | No content flashes more than 3 times |
| **2.3.3** Animation from Interactions | Motion animation can be disabled |
| **2.4.8** Location | User location within site is available |
| **2.4.9** Link Purpose (Link Only) | Link purpose clear from link text alone |
| **2.4.10** Section Headings | Sections have headings |
| **2.4.12** Focus Not Obscured (Enhanced) | No part of the focused element is hidden by author-created content |
| **2.4.13** Focus Appearance | Focus indicator has sufficient area, contrast, and is not obscured |
| **3.1.3** Unusual Words | Definitions available for unusual words |
| **3.1.4** Abbreviations | Abbreviations expanded |
| **3.1.5** Reading Level | Alternative content for complex text |
| **3.1.6** Pronunciation | Pronunciation available where needed |
| **3.2.5** Change on Request | Changes initiated only by user |
| **3.3.5** Help | Context-sensitive help available |
| **3.3.6** Error Prevention (All) | All form submissions can be reviewed |
| **3.3.9** Accessible Authentication (Enhanced) | No cognitive function test for login (no object or personal content recognition exceptions) |
## Common ARIA patterns
### Buttons
```html
<button>Label</button>
<!-- or -->
<button aria-label="Close dialog">×</button>
```
### Links
```html
<a href="/page">Descriptive link text</a>
<!-- External links -->
<a href="https://external.com" target="_blank" rel="noopener">
External site
<span class="visually-hidden">(opens in new tab)</span>
</a>
```
### Form fields
```html
<label for="email">Email address</label>
<input type="email" id="email" aria-describedby="email-hint">
<p id="email-hint">We'll never share your email.</p>
```
### Error states
```html
<label for="email">Email</label>
<input type="email" id="email" aria-invalid="true" aria-describedby="email-error">
<p id="email-error" role="alert">Please enter a valid email address.</p>
```
### Navigation
```html
<nav aria-label="Main">
<ul>
<li><a href="/" aria-current="page">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
```
### Modals
```html
<div role="dialog" aria-modal="true" aria-labelledby="dialog-title">
<h2 id="dialog-title">Confirm Action</h2>
<!-- content -->
</div>
```
### Live regions
```html
<!-- Polite (waits for pause in speech) -->
<div aria-live="polite">Status update here</div>
<!-- Assertive (interrupts immediately) -->
<div aria-live="assertive" role="alert">Error message here</div>
<!-- Status (polite, implicit) -->
<div role="status">Loading complete</div>
```
## What changed from 2.1 to 2.2
| Change | Criterion | Level |
|--------|-----------|-------|
| **Removed** | 4.1.1 Parsing | A |
| **Added** | 2.4.11 Focus Not Obscured (Minimum) | AA |
| **Added** | 2.4.12 Focus Not Obscured (Enhanced) | AAA |
| **Added** | 2.4.13 Focus Appearance | AAA |
| **Added** | 2.5.7 Dragging Movements | AA |
| **Added** | 2.5.8 Target Size (Minimum) | AA |
| **Added** | 3.2.6 Consistent Help | A |
| **Added** | 3.3.7 Redundant Entry | A |
| **Added** | 3.3.8 Accessible Authentication (Minimum) | AA |
| **Added** | 3.3.9 Accessible Authentication (Enhanced) | AAA |
## Testing tools
| Tool | Type | URL |
|------|------|-----|
| axe DevTools | Browser extension | [deque.com/axe](https://www.deque.com/axe/) |
| WAVE | Browser extension | [wave.webaim.org](https://wave.webaim.org/) |
| Lighthouse | Built into Chrome | DevTools → Lighthouse |
| NVDA | Screen reader (Windows) | [nvaccess.org](https://www.nvaccess.org/) |
| VoiceOver | Screen reader (Mac) | Built into macOS |
| Colour Contrast Analyser | Desktop app | [tpgi.com](https://www.tpgi.com/color-contrast-checker/) |
## Sources
- [WCAG 2.2 W3C Recommendation](https://www.w3.org/TR/WCAG22/)
- [WCAG 2.2 Quick Reference](https://www.w3.org/WAI/WCAG22/quickref/)
- [What's New in WCAG 2.2](https://www.w3.org/WAI/standards-guidelines/wcag/new-in-22/)
@@ -0,0 +1,177 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
@@ -0,0 +1,42 @@
---
name: frontend-design
description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.
license: Complete terms in LICENSE.txt
---
This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.
## Design Thinking
Before coding, understand the context and commit to a BOLD aesthetic direction:
- **Purpose**: What problem does this interface solve? Who uses it?
- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.
- **Constraints**: Technical requirements (framework, performance, accessibility).
- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?
**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.
Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is:
- Production-grade and functional
- Visually striking and memorable
- Cohesive with a clear aesthetic point-of-view
- Meticulously refined in every detail
## Frontend Aesthetics Guidelines
Focus on:
- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.
- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.
- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.
- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.
- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.
NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.
Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.
**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.
Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.
@@ -0,0 +1,639 @@
---
name: nodejs-backend-patterns
description: Build production-ready Node.js backend services with Express/Fastify, implementing middleware patterns, error handling, authentication, database integration, and API design best practices. Use when creating Node.js servers, REST APIs, GraphQL backends, or microservices architectures.
---
# Node.js Backend Patterns
Comprehensive guidance for building scalable, maintainable, and production-ready Node.js backend applications with modern frameworks, architectural patterns, and best practices.
## When to Use This Skill
- Building REST APIs or GraphQL servers
- Creating microservices with Node.js
- Implementing authentication and authorization
- Designing scalable backend architectures
- Setting up middleware and error handling
- Integrating databases (SQL and NoSQL)
- Building real-time applications with WebSockets
- Implementing background job processing
## Core Frameworks
### Express.js - Minimalist Framework
**Basic Setup:**
```typescript
import express, { Request, Response, NextFunction } from "express";
import helmet from "helmet";
import cors from "cors";
import compression from "compression";
const app = express();
// Security middleware
app.use(helmet());
app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(",") }));
app.use(compression());
// Body parsing
app.use(express.json({ limit: "10mb" }));
app.use(express.urlencoded({ extended: true, limit: "10mb" }));
// Request logging
app.use((req: Request, res: Response, next: NextFunction) => {
console.log(`${req.method} ${req.path}`);
next();
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
```
### Fastify - High Performance Framework
**Basic Setup:**
```typescript
import Fastify from "fastify";
import helmet from "@fastify/helmet";
import cors from "@fastify/cors";
import compress from "@fastify/compress";
const fastify = Fastify({
logger: {
level: process.env.LOG_LEVEL || "info",
transport: {
target: "pino-pretty",
options: { colorize: true },
},
},
});
// Plugins
await fastify.register(helmet);
await fastify.register(cors, { origin: true });
await fastify.register(compress);
// Type-safe routes with schema validation
fastify.post<{
Body: { name: string; email: string };
Reply: { id: string; name: string };
}>(
"/users",
{
schema: {
body: {
type: "object",
required: ["name", "email"],
properties: {
name: { type: "string", minLength: 1 },
email: { type: "string", format: "email" },
},
},
},
},
async (request, reply) => {
const { name, email } = request.body;
return { id: "123", name };
},
);
await fastify.listen({ port: 3000, host: "0.0.0.0" });
```
## Architectural Patterns
### Pattern 1: Layered Architecture
**Structure:**
```
src/
├── controllers/ # Handle HTTP requests/responses
├── services/ # Business logic
├── repositories/ # Data access layer
├── models/ # Data models
├── middleware/ # Express/Fastify middleware
├── routes/ # Route definitions
├── utils/ # Helper functions
├── config/ # Configuration
└── types/ # TypeScript types
```
**Controller Layer:**
```typescript
// controllers/user.controller.ts
import { Request, Response, NextFunction } from "express";
import { UserService } from "../services/user.service";
import { CreateUserDTO, UpdateUserDTO } from "../types/user.types";
export class UserController {
constructor(private userService: UserService) {}
async createUser(req: Request, res: Response, next: NextFunction) {
try {
const userData: CreateUserDTO = req.body;
const user = await this.userService.createUser(userData);
res.status(201).json(user);
} catch (error) {
next(error);
}
}
async getUser(req: Request, res: Response, next: NextFunction) {
try {
const { id } = req.params;
const user = await this.userService.getUserById(id);
res.json(user);
} catch (error) {
next(error);
}
}
async updateUser(req: Request, res: Response, next: NextFunction) {
try {
const { id } = req.params;
const updates: UpdateUserDTO = req.body;
const user = await this.userService.updateUser(id, updates);
res.json(user);
} catch (error) {
next(error);
}
}
async deleteUser(req: Request, res: Response, next: NextFunction) {
try {
const { id } = req.params;
await this.userService.deleteUser(id);
res.status(204).send();
} catch (error) {
next(error);
}
}
}
```
**Service Layer:**
```typescript
// services/user.service.ts
import { UserRepository } from "../repositories/user.repository";
import { CreateUserDTO, UpdateUserDTO, User } from "../types/user.types";
import { NotFoundError, ValidationError } from "../utils/errors";
import bcrypt from "bcrypt";
export class UserService {
constructor(private userRepository: UserRepository) {}
async createUser(userData: CreateUserDTO): Promise<User> {
// Validation
const existingUser = await this.userRepository.findByEmail(userData.email);
if (existingUser) {
throw new ValidationError("Email already exists");
}
// Hash password
const hashedPassword = await bcrypt.hash(userData.password, 10);
// Create user
const user = await this.userRepository.create({
...userData,
password: hashedPassword,
});
// Remove password from response
const { password, ...userWithoutPassword } = user;
return userWithoutPassword as User;
}
async getUserById(id: string): Promise<User> {
const user = await this.userRepository.findById(id);
if (!user) {
throw new NotFoundError("User not found");
}
const { password, ...userWithoutPassword } = user;
return userWithoutPassword as User;
}
async updateUser(id: string, updates: UpdateUserDTO): Promise<User> {
const user = await this.userRepository.update(id, updates);
if (!user) {
throw new NotFoundError("User not found");
}
const { password, ...userWithoutPassword } = user;
return userWithoutPassword as User;
}
async deleteUser(id: string): Promise<void> {
const deleted = await this.userRepository.delete(id);
if (!deleted) {
throw new NotFoundError("User not found");
}
}
}
```
**Repository Layer:**
```typescript
// repositories/user.repository.ts
import { Pool } from "pg";
import { CreateUserDTO, UpdateUserDTO, UserEntity } from "../types/user.types";
export class UserRepository {
constructor(private db: Pool) {}
async create(
userData: CreateUserDTO & { password: string },
): Promise<UserEntity> {
const query = `
INSERT INTO users (name, email, password)
VALUES ($1, $2, $3)
RETURNING id, name, email, password, created_at, updated_at
`;
const { rows } = await this.db.query(query, [
userData.name,
userData.email,
userData.password,
]);
return rows[0];
}
async findById(id: string): Promise<UserEntity | null> {
const query = "SELECT * FROM users WHERE id = $1";
const { rows } = await this.db.query(query, [id]);
return rows[0] || null;
}
async findByEmail(email: string): Promise<UserEntity | null> {
const query = "SELECT * FROM users WHERE email = $1";
const { rows } = await this.db.query(query, [email]);
return rows[0] || null;
}
async update(id: string, updates: UpdateUserDTO): Promise<UserEntity | null> {
const fields = Object.keys(updates);
const values = Object.values(updates);
const setClause = fields
.map((field, idx) => `${field} = $${idx + 2}`)
.join(", ");
const query = `
UPDATE users
SET ${setClause}, updated_at = CURRENT_TIMESTAMP
WHERE id = $1
RETURNING *
`;
const { rows } = await this.db.query(query, [id, ...values]);
return rows[0] || null;
}
async delete(id: string): Promise<boolean> {
const query = "DELETE FROM users WHERE id = $1";
const { rowCount } = await this.db.query(query, [id]);
return rowCount > 0;
}
}
```
### Pattern 2: Dependency Injection
Use a DI container to wire up repositories, services, and controllers. For a full container implementation, see [references/advanced-patterns.md](references/advanced-patterns.md).
## Middleware Patterns
### Authentication Middleware
```typescript
// middleware/auth.middleware.ts
import { Request, Response, NextFunction } from "express";
import jwt from "jsonwebtoken";
import { UnauthorizedError } from "../utils/errors";
interface JWTPayload {
userId: string;
email: string;
}
declare global {
namespace Express {
interface Request {
user?: JWTPayload;
}
}
}
export const authenticate = async (
req: Request,
res: Response,
next: NextFunction,
) => {
try {
const token = req.headers.authorization?.replace("Bearer ", "");
if (!token) {
throw new UnauthorizedError("No token provided");
}
const payload = jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload;
req.user = payload;
next();
} catch (error) {
next(new UnauthorizedError("Invalid token"));
}
};
export const authorize = (...roles: string[]) => {
return async (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
return next(new UnauthorizedError("Not authenticated"));
}
// Check if user has required role
const hasRole = roles.some((role) => req.user?.roles?.includes(role));
if (!hasRole) {
return next(new UnauthorizedError("Insufficient permissions"));
}
next();
};
};
```
### Validation Middleware
```typescript
// middleware/validation.middleware.ts
import { Request, Response, NextFunction } from "express";
import { AnyZodObject, ZodError } from "zod";
import { ValidationError } from "../utils/errors";
export const validate = (schema: AnyZodObject) => {
return async (req: Request, res: Response, next: NextFunction) => {
try {
await schema.parseAsync({
body: req.body,
query: req.query,
params: req.params,
});
next();
} catch (error) {
if (error instanceof ZodError) {
const errors = error.errors.map((err) => ({
field: err.path.join("."),
message: err.message,
}));
next(new ValidationError("Validation failed", errors));
} else {
next(error);
}
}
};
};
// Usage with Zod
import { z } from "zod";
const createUserSchema = z.object({
body: z.object({
name: z.string().min(1),
email: z.string().email(),
password: z.string().min(8),
}),
});
router.post("/users", validate(createUserSchema), userController.createUser);
```
### Rate Limiting Middleware
```typescript
// middleware/rate-limit.middleware.ts
import rateLimit from "express-rate-limit";
import RedisStore from "rate-limit-redis";
import Redis from "ioredis";
const redis = new Redis({
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT || "6379"),
});
export const apiLimiter = rateLimit({
store: new RedisStore({
client: redis,
prefix: "rl:",
}),
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: "Too many requests from this IP, please try again later",
standardHeaders: true,
legacyHeaders: false,
});
export const authLimiter = rateLimit({
store: new RedisStore({
client: redis,
prefix: "rl:auth:",
}),
windowMs: 15 * 60 * 1000,
max: 5, // Stricter limit for auth endpoints
skipSuccessfulRequests: true,
});
```
### Request Logging Middleware
```typescript
// middleware/logger.middleware.ts
import { Request, Response, NextFunction } from "express";
import pino from "pino";
const logger = pino({
level: process.env.LOG_LEVEL || "info",
transport: {
target: "pino-pretty",
options: { colorize: true },
},
});
export const requestLogger = (
req: Request,
res: Response,
next: NextFunction,
) => {
const start = Date.now();
// Log response when finished
res.on("finish", () => {
const duration = Date.now() - start;
logger.info({
method: req.method,
url: req.url,
status: res.statusCode,
duration: `${duration}ms`,
userAgent: req.headers["user-agent"],
ip: req.ip,
});
});
next();
};
export { logger };
```
## Error Handling
### Custom Error Classes
```typescript
// utils/errors.ts
export class AppError extends Error {
constructor(
public message: string,
public statusCode: number = 500,
public isOperational: boolean = true,
) {
super(message);
Object.setPrototypeOf(this, AppError.prototype);
Error.captureStackTrace(this, this.constructor);
}
}
export class ValidationError extends AppError {
constructor(
message: string,
public errors?: any[],
) {
super(message, 400);
}
}
export class NotFoundError extends AppError {
constructor(message: string = "Resource not found") {
super(message, 404);
}
}
export class UnauthorizedError extends AppError {
constructor(message: string = "Unauthorized") {
super(message, 401);
}
}
export class ForbiddenError extends AppError {
constructor(message: string = "Forbidden") {
super(message, 403);
}
}
export class ConflictError extends AppError {
constructor(message: string) {
super(message, 409);
}
}
```
### Global Error Handler
```typescript
// middleware/error-handler.ts
import { Request, Response, NextFunction } from "express";
import { AppError } from "../utils/errors";
import { logger } from "./logger.middleware";
export const errorHandler = (
err: Error,
req: Request,
res: Response,
next: NextFunction,
) => {
if (err instanceof AppError) {
return res.status(err.statusCode).json({
status: "error",
message: err.message,
...(err instanceof ValidationError && { errors: err.errors }),
});
}
// Log unexpected errors
logger.error({
error: err.message,
stack: err.stack,
url: req.url,
method: req.method,
});
// Don't leak error details in production
const message =
process.env.NODE_ENV === "production"
? "Internal server error"
: err.message;
res.status(500).json({
status: "error",
message,
});
};
// Async error wrapper
export const asyncHandler = (
fn: (req: Request, res: Response, next: NextFunction) => Promise<any>,
) => {
return (req: Request, res: Response, next: NextFunction) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
};
```
## Database Patterns
Node.js supports both SQL and NoSQL databases. Use connection pooling for all production databases.
Key patterns covered in [references/advanced-patterns.md](references/advanced-patterns.md):
- **PostgreSQL with connection pool** — `pg` Pool configuration and graceful shutdown
- **MongoDB with Mongoose** — connection management and schema definition
- **Transaction pattern** — `BEGIN`/`COMMIT`/`ROLLBACK` with `pg` client
## Authentication & Authorization
JWT-based auth with access tokens (short-lived, 15m) and refresh tokens (7d). Full `AuthService` implementation with `bcrypt` password comparison in [references/advanced-patterns.md](references/advanced-patterns.md).
## Caching Strategies
Redis-backed `CacheService` with get/set/delete/invalidatePattern, plus a `@Cacheable` decorator for method-level caching. See [references/advanced-patterns.md](references/advanced-patterns.md).
## API Response Format
Standardized `ApiResponse` helper with `success`, `error`, and `paginated` static methods. See [references/advanced-patterns.md](references/advanced-patterns.md).
## Best Practices
1. **Use TypeScript**: Type safety prevents runtime errors
2. **Implement proper error handling**: Use custom error classes
3. **Validate input**: Use libraries like Zod or Joi
4. **Use environment variables**: Never hardcode secrets
5. **Implement logging**: Use structured logging (Pino, Winston)
6. **Add rate limiting**: Prevent abuse
7. **Use HTTPS**: Always in production
8. **Implement CORS properly**: Don't use `*` in production
9. **Use dependency injection**: Easier testing and maintenance
10. **Write tests**: Unit, integration, and E2E tests
11. **Handle graceful shutdown**: Clean up resources
12. **Use connection pooling**: For databases
13. **Implement health checks**: For monitoring
14. **Use compression**: Reduce response size
15. **Monitor performance**: Use APM tools
## Testing Patterns
See `javascript-testing-patterns` skill for comprehensive testing guidance.
@@ -0,0 +1,430 @@
# Node.js Advanced Patterns
Advanced patterns for dependency injection, database integration, authentication, caching, and API response formatting.
## Dependency Injection
### DI Container
```typescript
// di-container.ts
import { Pool } from "pg";
import { UserRepository } from "./repositories/user.repository";
import { UserService } from "./services/user.service";
import { UserController } from "./controllers/user.controller";
import { AuthService } from "./services/auth.service";
class Container {
private instances = new Map<string, any>();
register<T>(key: string, factory: () => T): void {
this.instances.set(key, factory);
}
resolve<T>(key: string): T {
const factory = this.instances.get(key);
if (!factory) {
throw new Error(`No factory registered for ${key}`);
}
return factory();
}
singleton<T>(key: string, factory: () => T): void {
let instance: T;
this.instances.set(key, () => {
if (!instance) {
instance = factory();
}
return instance;
});
}
}
export const container = new Container();
// Register dependencies
container.singleton(
"db",
() =>
new Pool({
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT || "5432"),
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
}),
);
container.singleton(
"userRepository",
() => new UserRepository(container.resolve("db")),
);
container.singleton(
"userService",
() => new UserService(container.resolve("userRepository")),
);
container.register(
"userController",
() => new UserController(container.resolve("userService")),
);
container.singleton(
"authService",
() => new AuthService(container.resolve("userRepository")),
);
```
## Database Patterns
### PostgreSQL with Connection Pool
```typescript
// config/database.ts
import { Pool, PoolConfig } from "pg";
const poolConfig: PoolConfig = {
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT || "5432"),
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
};
export const pool = new Pool(poolConfig);
// Test connection
pool.on("connect", () => {
console.log("Database connected");
});
pool.on("error", (err) => {
console.error("Unexpected database error", err);
process.exit(-1);
});
// Graceful shutdown
export const closeDatabase = async () => {
await pool.end();
console.log("Database connection closed");
};
```
### MongoDB with Mongoose
```typescript
// config/mongoose.ts
import mongoose from "mongoose";
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGODB_URI!, {
maxPoolSize: 10,
serverSelectionTimeoutMS: 5000,
socketTimeoutMS: 45000,
});
console.log("MongoDB connected");
} catch (error) {
console.error("MongoDB connection error:", error);
process.exit(1);
}
};
mongoose.connection.on("disconnected", () => {
console.log("MongoDB disconnected");
});
mongoose.connection.on("error", (err) => {
console.error("MongoDB error:", err);
});
export { connectDB };
// Model example
import { Schema, model, Document } from "mongoose";
interface IUser extends Document {
name: string;
email: string;
password: string;
createdAt: Date;
updatedAt: Date;
}
const userSchema = new Schema<IUser>(
{
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
},
{
timestamps: true,
},
);
// Indexes
userSchema.index({ email: 1 });
export const User = model<IUser>("User", userSchema);
```
### Transaction Pattern
```typescript
// services/order.service.ts
import { Pool } from "pg";
export class OrderService {
constructor(private db: Pool) {}
async createOrder(userId: string, items: any[]) {
const client = await this.db.connect();
try {
await client.query("BEGIN");
// Create order
const orderResult = await client.query(
"INSERT INTO orders (user_id, total) VALUES ($1, $2) RETURNING id",
[userId, calculateTotal(items)],
);
const orderId = orderResult.rows[0].id;
// Create order items
for (const item of items) {
await client.query(
"INSERT INTO order_items (order_id, product_id, quantity, price) VALUES ($1, $2, $3, $4)",
[orderId, item.productId, item.quantity, item.price],
);
// Update inventory
await client.query(
"UPDATE products SET stock = stock - $1 WHERE id = $2",
[item.quantity, item.productId],
);
}
await client.query("COMMIT");
return orderId;
} catch (error) {
await client.query("ROLLBACK");
throw error;
} finally {
client.release();
}
}
}
```
## Authentication & Authorization
### JWT Authentication
```typescript
// services/auth.service.ts
import jwt from "jsonwebtoken";
import bcrypt from "bcrypt";
import { UserRepository } from "../repositories/user.repository";
import { UnauthorizedError } from "../utils/errors";
export class AuthService {
constructor(private userRepository: UserRepository) {}
async login(email: string, password: string) {
const user = await this.userRepository.findByEmail(email);
if (!user) {
throw new UnauthorizedError("Invalid credentials");
}
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
throw new UnauthorizedError("Invalid credentials");
}
const token = this.generateToken({
userId: user.id,
email: user.email,
});
const refreshToken = this.generateRefreshToken({
userId: user.id,
});
return {
token,
refreshToken,
user: {
id: user.id,
name: user.name,
email: user.email,
},
};
}
async refreshToken(refreshToken: string) {
try {
const payload = jwt.verify(
refreshToken,
process.env.REFRESH_TOKEN_SECRET!,
) as { userId: string };
const user = await this.userRepository.findById(payload.userId);
if (!user) {
throw new UnauthorizedError("User not found");
}
const token = this.generateToken({
userId: user.id,
email: user.email,
});
return { token };
} catch (error) {
throw new UnauthorizedError("Invalid refresh token");
}
}
private generateToken(payload: any): string {
return jwt.sign(payload, process.env.JWT_SECRET!, {
expiresIn: "15m",
});
}
private generateRefreshToken(payload: any): string {
return jwt.sign(payload, process.env.REFRESH_TOKEN_SECRET!, {
expiresIn: "7d",
});
}
}
```
## Caching Strategies
```typescript
// utils/cache.ts
import Redis from "ioredis";
const redis = new Redis({
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT || "6379"),
retryStrategy: (times) => {
const delay = Math.min(times * 50, 2000);
return delay;
},
});
export class CacheService {
async get<T>(key: string): Promise<T | null> {
const data = await redis.get(key);
return data ? JSON.parse(data) : null;
}
async set(key: string, value: any, ttl?: number): Promise<void> {
const serialized = JSON.stringify(value);
if (ttl) {
await redis.setex(key, ttl, serialized);
} else {
await redis.set(key, serialized);
}
}
async delete(key: string): Promise<void> {
await redis.del(key);
}
async invalidatePattern(pattern: string): Promise<void> {
const keys = await redis.keys(pattern);
if (keys.length > 0) {
await redis.del(...keys);
}
}
}
// Cache decorator
export function Cacheable(ttl: number = 300) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor,
) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
const cache = new CacheService();
const cacheKey = `${propertyKey}:${JSON.stringify(args)}`;
const cached = await cache.get(cacheKey);
if (cached) {
return cached;
}
const result = await originalMethod.apply(this, args);
await cache.set(cacheKey, result, ttl);
return result;
};
return descriptor;
};
}
```
## API Response Format
```typescript
// utils/response.ts
import { Response } from "express";
export class ApiResponse {
static success<T>(
res: Response,
data: T,
message?: string,
statusCode = 200,
) {
return res.status(statusCode).json({
status: "success",
message,
data,
});
}
static error(res: Response, message: string, statusCode = 500, errors?: any) {
return res.status(statusCode).json({
status: "error",
message,
...(errors && { errors }),
});
}
static paginated<T>(
res: Response,
data: T[],
page: number,
limit: number,
total: number,
) {
return res.json({
status: "success",
data,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit),
},
});
}
}
```
@@ -0,0 +1,343 @@
---
name: nodejs-best-practices
description: "Node.js development principles and decision-making. Framework selection, async patterns, security, and architecture. Teaches thinking, not copying."
risk: unknown
source: community
date_added: "2026-02-27"
---
# Node.js Best Practices
> Principles and decision-making for Node.js development in 2025.
> **Learn to THINK, not memorize code patterns.**
## When to Use
Use this skill when making Node.js architecture decisions, choosing frameworks, designing async patterns, or applying security and deployment best practices.
---
## ⚠️ How to Use This Skill
This skill teaches **decision-making principles**, not fixed code to copy.
- ASK user for preferences when unclear
- Choose framework/pattern based on CONTEXT
- Don't default to same solution every time
---
## 1. Framework Selection (2025)
### Decision Tree
```
What are you building?
│
├── Edge/Serverless (Cloudflare, Vercel)
│ └── Hono (zero-dependency, ultra-fast cold starts)
│
├── High Performance API
│ └── Fastify (2-3x faster than Express)
│
├── Enterprise/Team familiarity
│ └── NestJS (structured, DI, decorators)
│
├── Legacy/Stable/Maximum ecosystem
│ └── Express (mature, most middleware)
│
└── Full-stack with frontend
└── Next.js API Routes or tRPC
```
### Comparison Principles
| Factor | Hono | Fastify | Express |
|--------|------|---------|---------|
| **Best for** | Edge, serverless | Performance | Legacy, learning |
| **Cold start** | Fastest | Fast | Moderate |
| **Ecosystem** | Growing | Good | Largest |
| **TypeScript** | Native | Excellent | Good |
| **Learning curve** | Low | Medium | Low |
### Selection Questions to Ask:
1. What's the deployment target?
2. Is cold start time critical?
3. Does team have existing experience?
4. Is there legacy code to maintain?
---
## 2. Runtime Considerations (2025)
### Native TypeScript
```
Node.js 22+: --experimental-strip-types
├── Run .ts files directly
├── No build step needed for simple projects
└── Consider for: scripts, simple APIs
```
### Module System Decision
```
ESM (import/export)
├── Modern standard
├── Better tree-shaking
├── Async module loading
└── Use for: new projects
CommonJS (require)
├── Legacy compatibility
├── More npm packages support
└── Use for: existing codebases, some edge cases
```
### Runtime Selection
| Runtime | Best For |
|---------|----------|
| **Node.js** | General purpose, largest ecosystem |
| **Bun** | Performance, built-in bundler |
| **Deno** | Security-first, built-in TypeScript |
---
## 3. Architecture Principles
### Layered Structure Concept
```
Request Flow:
│
├── Controller/Route Layer
│ ├── Handles HTTP specifics
│ ├── Input validation at boundary
│ └── Calls service layer
│
├── Service Layer
│ ├── Business logic
│ ├── Framework-agnostic
│ └── Calls repository layer
│
└── Repository Layer
├── Data access only
├── Database queries
└── ORM interactions
```
### Why This Matters:
- **Testability**: Mock layers independently
- **Flexibility**: Swap database without touching business logic
- **Clarity**: Each layer has single responsibility
### When to Simplify:
- Small scripts → Single file OK
- Prototypes → Less structure acceptable
- Always ask: "Will this grow?"
---
## 4. Error Handling Principles
### Centralized Error Handling
```
Pattern:
├── Create custom error classes
├── Throw from any layer
├── Catch at top level (middleware)
└── Format consistent response
```
### Error Response Philosophy
```
Client gets:
├── Appropriate HTTP status
├── Error code for programmatic handling
├── User-friendly message
└── NO internal details (security!)
Logs get:
├── Full stack trace
├── Request context
├── User ID (if applicable)
└── Timestamp
```
### Status Code Selection
| Situation | Status | When |
|-----------|--------|------|
| Bad input | 400 | Client sent invalid data |
| No auth | 401 | Missing or invalid credentials |
| No permission | 403 | Valid auth, but not allowed |
| Not found | 404 | Resource doesn't exist |
| Conflict | 409 | Duplicate or state conflict |
| Validation | 422 | Schema valid but business rules fail |
| Server error | 500 | Our fault, log everything |
---
## 5. Async Patterns Principles
### When to Use Each
| Pattern | Use When |
|---------|----------|
| `async/await` | Sequential async operations |
| `Promise.all` | Parallel independent operations |
| `Promise.allSettled` | Parallel where some can fail |
| `Promise.race` | Timeout or first response wins |
### Event Loop Awareness
```
I/O-bound (async helps):
├── Database queries
├── HTTP requests
├── File system
└── Network operations
CPU-bound (async doesn't help):
├── Crypto operations
├── Image processing
├── Complex calculations
└── → Use worker threads or offload
```
### Avoiding Event Loop Blocking
- Never use sync methods in production (fs.readFileSync, etc.)
- Offload CPU-intensive work
- Use streaming for large data
---
## 6. Validation Principles
### Validate at Boundaries
```
Where to validate:
├── API entry point (request body/params)
├── Before database operations
├── External data (API responses, file uploads)
└── Environment variables (startup)
```
### Validation Library Selection
| Library | Best For |
|---------|----------|
| **Zod** | TypeScript first, inference |
| **Valibot** | Smaller bundle (tree-shakeable) |
| **ArkType** | Performance critical |
| **Yup** | Existing React Form usage |
### Validation Philosophy
- Fail fast: Validate early
- Be specific: Clear error messages
- Don't trust: Even "internal" data
---
## 7. Security Principles
### Security Checklist (Not Code)
- [ ] **Input validation**: All inputs validated
- [ ] **Parameterized queries**: No string concatenation for SQL
- [ ] **Password hashing**: bcrypt or argon2
- [ ] **JWT verification**: Always verify signature and expiry
- [ ] **Rate limiting**: Protect from abuse
- [ ] **Security headers**: Helmet.js or equivalent
- [ ] **HTTPS**: Everywhere in production
- [ ] **CORS**: Properly configured
- [ ] **Secrets**: Environment variables only
- [ ] **Dependencies**: Regularly audited
### Security Mindset
```
Trust nothing:
├── Query params → validate
├── Request body → validate
├── Headers → verify
├── Cookies → validate
├── File uploads → scan
└── External APIs → validate response
```
---
## 8. Testing Principles
### Test Strategy Selection
| Type | Purpose | Tools |
|------|---------|-------|
| **Unit** | Business logic | node:test, Vitest |
| **Integration** | API endpoints | Supertest |
| **E2E** | Full flows | Playwright |
### What to Test (Priorities)
1. **Critical paths**: Auth, payments, core business
2. **Edge cases**: Empty inputs, boundaries
3. **Error handling**: What happens when things fail?
4. **Not worth testing**: Framework code, trivial getters
### Built-in Test Runner (Node.js 22+)
```
node --test src/**/*.test.ts
├── No external dependency
├── Good coverage reporting
└── Watch mode available
```
---
## 9. Anti-Patterns to Avoid
### ❌ DON'T:
- Use Express for new edge projects (use Hono)
- Use sync methods in production code
- Put business logic in controllers
- Skip input validation
- Hardcode secrets
- Trust external data without validation
- Block event loop with CPU work
### ✅ DO:
- Choose framework based on context
- Ask user for preferences when unclear
- Use layered architecture for growing projects
- Validate all inputs
- Use environment variables for secrets
- Profile before optimizing
---
## 10. Decision Checklist
Before implementing:
- [ ] **Asked user about stack preference?**
- [ ] **Chosen framework for THIS context?** (not just default)
- [ ] **Considered deployment target?**
- [ ] **Planned error handling strategy?**
- [ ] **Identified validation points?**
- [ ] **Considered security requirements?**
---
> **Remember**: Node.js best practices are about decision-making, not memorizing patterns. Every project deserves fresh consideration based on its requirements.
## Limitations
- Use this skill only when the task clearly matches the scope described above.
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.
+513
View File
@@ -0,0 +1,513 @@
---
name: seo
description: Optimize for search engine visibility and ranking. Use when asked to "improve SEO", "optimize for search", "fix meta tags", "add structured data", "sitemap optimization", or "search engine optimization".
license: MIT
metadata:
author: web-quality-skills
version: "1.0"
---
# SEO optimization
Search engine optimization based on Lighthouse SEO audits and Google Search guidelines. Focus on technical SEO, on-page optimization, and structured data.
## SEO fundamentals
Search ranking factors (approximate influence):
| Factor | Influence | This Skill |
|--------|-----------|------------|
| Content quality & relevance | ~40% | Partial (structure) |
| Backlinks & authority | ~25% | ✗ |
| Technical SEO | ~15% | ✓ |
| Page experience (Core Web Vitals) | ~10% | See [Core Web Vitals](../core-web-vitals/SKILL.md) |
| On-page SEO | ~10% | ✓ |
---
## Technical SEO
### Crawlability
**robots.txt:**
```text
# /robots.txt
User-agent: *
Allow: /
# Block admin/private areas
Disallow: /admin/
Disallow: /api/
Disallow: /private/
# Don't block resources needed for rendering
# ❌ Disallow: /static/
Sitemap: https://example.com/sitemap.xml
```
**Meta robots:**
```html
<!-- Default: indexable, followable -->
<meta name="robots" content="index, follow">
<!-- Noindex specific pages -->
<meta name="robots" content="noindex, nofollow">
<!-- Indexable but don't follow links -->
<meta name="robots" content="index, nofollow">
<!-- Control snippets -->
<meta name="robots" content="max-snippet:150, max-image-preview:large">
```
**Canonical URLs:**
```html
<!-- Prevent duplicate content issues -->
<link rel="canonical" href="https://example.com/page">
<!-- Self-referencing canonical (recommended) -->
<link rel="canonical" href="https://example.com/current-page">
<!-- For paginated content -->
<link rel="canonical" href="https://example.com/products">
<!-- Or use rel="prev" / rel="next" for explicit pagination -->
```
### XML sitemap
```xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/</loc>
<lastmod>2024-01-15</lastmod>
<changefreq>daily</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://example.com/products</loc>
<lastmod>2024-01-14</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
</urlset>
```
**Sitemap best practices:**
- Maximum 50,000 URLs or 50MB per sitemap
- Use sitemap index for larger sites
- Include only canonical, indexable URLs
- Update `lastmod` when content changes
- Submit to Google Search Console
### URL structure
```
✅ Good URLs:
https://example.com/products/blue-widget
https://example.com/blog/how-to-use-widgets
❌ Poor URLs:
https://example.com/p?id=12345
https://example.com/products/item/category/subcategory/blue-widget-2024-sale-discount
```
**URL guidelines:**
- Use hyphens, not underscores
- Lowercase only
- Keep short (< 75 characters)
- Include target keywords naturally
- Avoid parameters when possible
- Use HTTPS always
### HTTPS & security
```html
<!-- Ensure all resources use HTTPS -->
<img src="https://example.com/image.jpg">
<!-- Not: -->
<img src="http://example.com/image.jpg">
```
**Security headers for SEO trust signals:**
```
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
```
---
## On-page SEO
### Title tags
```html
<!-- ❌ Missing or generic -->
<title>Page</title>
<title>Home</title>
<!-- ✅ Descriptive with primary keyword -->
<title>Blue Widgets for Sale | Premium Quality | Example Store</title>
```
**Title tag guidelines:**
- 50-60 characters (Google truncates ~60)
- Primary keyword near the beginning
- Unique for every page
- Brand name at end (unless homepage)
- Action-oriented when appropriate
### Meta descriptions
```html
<!-- ❌ Missing or duplicate -->
<meta name="description" content="">
<!-- ✅ Compelling and unique -->
<meta name="description" content="Shop premium blue widgets with free shipping. 30-day returns. Rated 4.9/5 by 10,000+ customers. Order today and save 20%.">
```
**Meta description guidelines:**
- 150-160 characters
- Include primary keyword naturally
- Compelling call-to-action
- Unique for every page
- Matches page content
### Heading structure
```html
<!-- ❌ Poor structure -->
<h2>Welcome to Our Store</h2>
<h4>Products</h4>
<h1>Contact Us</h1>
<!-- ✅ Proper hierarchy -->
<h1>Blue Widgets - Premium Quality</h1>
<h2>Product Features</h2>
<h3>Durability</h3>
<h3>Design</h3>
<h2>Customer Reviews</h2>
<h2>Pricing</h2>
```
**Heading guidelines:**
- Single `<h1>` per page (the main topic)
- Logical hierarchy (don't skip levels)
- Include keywords naturally
- Descriptive, not generic
### Image SEO
```html
<!-- ❌ Poor image SEO -->
<img src="IMG_12345.jpg">
<!-- ✅ Optimized image -->
<img src="blue-widget-product-photo.webp"
alt="Blue widget with chrome finish, side view showing control panel"
width="800"
height="600"
loading="lazy">
```
**Image guidelines:**
- Descriptive filenames with keywords
- Alt text describes the image content
- Compressed and properly sized
- WebP/AVIF with fallbacks
- Lazy load below-fold images
### Internal linking
```html
<!-- ❌ Non-descriptive -->
<a href="/products">Click here</a>
<a href="/widgets">Read more</a>
<!-- ✅ Descriptive anchor text -->
<a href="/products/blue-widgets">Browse our blue widget collection</a>
<a href="/guides/widget-maintenance">Learn how to maintain your widgets</a>
```
**Linking guidelines:**
- Descriptive anchor text with keywords
- Link to relevant internal pages
- Reasonable number of links per page
- Fix broken links promptly
- Use breadcrumbs for hierarchy
---
## Structured data (JSON-LD)
### Organization
```html
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Example Company",
"url": "https://example.com",
"logo": "https://example.com/logo.png",
"sameAs": [
"https://twitter.com/example",
"https://linkedin.com/company/example"
],
"contactPoint": {
"@type": "ContactPoint",
"telephone": "+1-555-123-4567",
"contactType": "customer service"
}
}
</script>
```
### Article
```html
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "How to Choose the Right Widget",
"description": "Complete guide to selecting widgets for your needs.",
"image": "https://example.com/article-image.jpg",
"author": {
"@type": "Person",
"name": "Jane Smith",
"url": "https://example.com/authors/jane-smith"
},
"publisher": {
"@type": "Organization",
"name": "Example Blog",
"logo": {
"@type": "ImageObject",
"url": "https://example.com/logo.png"
}
},
"datePublished": "2024-01-15",
"dateModified": "2024-01-20"
}
</script>
```
### Product
```html
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Blue Widget Pro",
"image": "https://example.com/blue-widget.jpg",
"description": "Premium blue widget with advanced features.",
"brand": {
"@type": "Brand",
"name": "WidgetCo"
},
"offers": {
"@type": "Offer",
"price": "49.99",
"priceCurrency": "USD",
"availability": "https://schema.org/InStock",
"url": "https://example.com/products/blue-widget"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.8",
"reviewCount": "1250"
}
}
</script>
```
### FAQ
```html
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "What colors are available?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Our widgets come in blue, red, and green."
}
},
{
"@type": "Question",
"name": "What is the warranty?",
"acceptedAnswer": {
"@type": "Answer",
"text": "All widgets include a 2-year warranty."
}
}
]
}
</script>
```
### Breadcrumbs
```html
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "https://example.com"
},
{
"@type": "ListItem",
"position": 2,
"name": "Products",
"item": "https://example.com/products"
},
{
"@type": "ListItem",
"position": 3,
"name": "Blue Widgets",
"item": "https://example.com/products/blue-widgets"
}
]
}
</script>
```
### Validation
Test structured data at:
- [Google Rich Results Test](https://search.google.com/test/rich-results)
- [Schema.org Validator](https://validator.schema.org/)
---
## Mobile SEO
### Responsive design
```html
<!-- ❌ Not mobile-friendly -->
<meta name="viewport" content="width=1024">
<!-- ✅ Responsive viewport -->
<meta name="viewport" content="width=device-width, initial-scale=1">
```
### Tap targets
```css
/* ❌ Too small for mobile */
.small-link {
padding: 4px;
font-size: 12px;
}
/* ✅ Adequate tap target */
.mobile-friendly-link {
padding: 12px;
font-size: 16px;
min-height: 48px;
min-width: 48px;
}
```
### Font sizes
```css
/* ❌ Too small on mobile */
body {
font-size: 10px;
}
/* ✅ Readable without zooming */
body {
font-size: 16px;
line-height: 1.5;
}
```
---
## International SEO
### Hreflang tags
```html
<!-- For multi-language sites -->
<link rel="alternate" hreflang="en" href="https://example.com/page">
<link rel="alternate" hreflang="es" href="https://example.com/es/page">
<link rel="alternate" hreflang="fr" href="https://example.com/fr/page">
<link rel="alternate" hreflang="x-default" href="https://example.com/page">
```
### Language declaration
```html
<html lang="en">
<!-- or -->
<html lang="es-MX">
```
---
## SEO audit checklist
### Critical
- [ ] HTTPS enabled
- [ ] robots.txt allows crawling
- [ ] No `noindex` on important pages
- [ ] Title tags present and unique
- [ ] Single `<h1>` per page
### High priority
- [ ] Meta descriptions present
- [ ] Sitemap submitted
- [ ] Canonical URLs set
- [ ] Mobile-responsive
- [ ] Core Web Vitals passing
### Medium priority
- [ ] Structured data implemented
- [ ] Internal linking strategy
- [ ] Image alt text
- [ ] Descriptive URLs
- [ ] Breadcrumb navigation
### Ongoing
- [ ] Fix crawl errors in Search Console
- [ ] Update sitemap when content changes
- [ ] Monitor ranking changes
- [ ] Check for broken links
- [ ] Review Search Console insights
---
## Tools
| Tool | Use |
|------|-----|
| Google Search Console | Monitor indexing, fix issues |
| Google PageSpeed Insights | Performance + Core Web Vitals |
| Rich Results Test | Validate structured data |
| Lighthouse | Full SEO audit |
| Screaming Frog | Crawl analysis |
## References
- [Google Search Central](https://developers.google.com/search)
- [Schema.org](https://schema.org/)
- [Core Web Vitals](../core-web-vitals/SKILL.md)
- [Web Quality Audit](../web-quality-audit/SKILL.md)
@@ -0,0 +1,66 @@
---
name: svelte-code-writer
description: CLI tools for Svelte 5 documentation lookup and code analysis. MUST be used whenever creating, editing or analyzing any Svelte component (.svelte) or Svelte module (.svelte.ts/.svelte.js). If possible, this skill should be executed within the svelte-file-editor agent for optimal results.
---
# Svelte 5 Code Writer
## CLI Tools
You have access to `@sveltejs/mcp` CLI for Svelte-specific assistance. Use these commands via `npx`:
### List Documentation Sections
```bash
npx @sveltejs/mcp list-sections
```
Lists all available Svelte 5 and SvelteKit documentation sections with titles and paths.
### Get Documentation
```bash
npx @sveltejs/mcp get-documentation "<section1>,<section2>,..."
```
Retrieves full documentation for specified sections. Use after `list-sections` to fetch relevant docs.
**Example:**
```bash
npx @sveltejs/mcp get-documentation "$state,$derived,$effect"
```
### Svelte Autofixer
```bash
npx @sveltejs/mcp svelte-autofixer "<code_or_path>" [options]
```
Analyzes Svelte code and suggests fixes for common issues.
**Options:**
- `--async` - Enable async Svelte mode (default: false)
- `--svelte-version` - Target version: 4 or 5 (default: 5)
**Examples:**
```bash
# Analyze inline code (escape $ as \$)
npx @sveltejs/mcp svelte-autofixer '<script>let count = \$state(0);</script>'
# Analyze a file
npx @sveltejs/mcp svelte-autofixer ./src/lib/Component.svelte
# Target Svelte 4
npx @sveltejs/mcp svelte-autofixer ./Component.svelte --svelte-version 4
```
**Important:** When passing code with runes (`$state`, `$derived`, etc.) via the terminal, escape the `$` character as `\$` to prevent shell variable substitution.
## Workflow
1. **Uncertain about syntax?** Run `list-sections` then `get-documentation` for relevant topics
2. **Reviewing/debugging?** Run `svelte-autofixer` on the code to detect issues
3. **Always validate** - Run `svelte-autofixer` before finalizing any Svelte component
@@ -0,0 +1,81 @@
---
name: svelte5-best-practices
description: "Svelte 5 runes, snippets, SvelteKit patterns, and modern best practices for TypeScript and component development. Use when writing, reviewing, or refactoring Svelte 5 components and SvelteKit applications. Triggers on: Svelte components, runes ($state, $derived, $effect, $props, $bindable, $inspect), snippets ({#snippet}, {@render}), event handling, SvelteKit data loading, form actions, Svelte 4 to Svelte 5 migration, store to rune migration, slots to snippets migration, TypeScript props typing, generic components, SSR state isolation, performance optimization, or component testing."
license: MIT
metadata:
author: ejirocodes
version: '1.0.0'
---
# Svelte 5 Best Practices
## Quick Reference
| Topic | When to Use | Reference |
|-------|-------------|-----------|
| **Runes** | $state, $derived, $effect, $props, $bindable, $inspect | [runes.md](references/runes.md) |
| **Snippets** | Replacing slots, {#snippet}, {@render} | [snippets.md](references/snippets.md) |
| **Events** | onclick handlers, callback props, context API | [events.md](references/events.md) |
| **TypeScript** | Props typing, generic components | [typescript.md](references/typescript.md) |
| **Migration** | Svelte 4 to 5, stores to runes | [migration.md](references/migration.md) |
| **SvelteKit** | Load functions, form actions, SSR, page typing | [sveltekit.md](references/sveltekit.md) |
| **Performance** | Universal reactivity, avoiding over-reactivity, streaming | [performance.md](references/performance.md) |
## Essential Patterns
### Reactive State
```svelte
<script>
let count = $state(0); // Reactive state
let doubled = $derived(count * 2); // Computed value
</script>
```
### Component Props
```svelte
<script>
let { name, count = 0 } = $props();
let { value = $bindable() } = $props(); // Two-way binding
</script>
```
### Snippets (replacing slots)
```svelte
<script>
let { children, header } = $props();
</script>
{@render header?.()}
{@render children()}
```
### Event Handlers
```svelte
<!-- Svelte 5: use onclick, not on:click -->
<button onclick={() => count++}>Click</button>
```
### Callback Props (replacing createEventDispatcher)
```svelte
<script>
let { onclick } = $props();
</script>
<button onclick={() => onclick?.({ data })}>Click</button>
```
## Common Mistakes
1. **Using `let` without `$state`** - Variables are not reactive without `$state()`
2. **Using `$effect` for derived values** - Use `$derived` instead
3. **Using `on:click` syntax** - Use `onclick` in Svelte 5
4. **Using `createEventDispatcher`** - Use callback props instead
5. **Using `<slot>`** - Use snippets with `{@render}`
6. **Forgetting `$bindable()`** - Required for `bind:` to work
7. **Setting module-level state in SSR** - Causes cross-request leaks
8. **Sequential awaits in load functions** - Use `Promise.all` for parallel requests
@@ -0,0 +1,353 @@
# Svelte 5 Events Reference
## Table of Contents
- [Event Handler Syntax](#event-handler-syntax)
- [Callback Props Pattern](#callback-props-pattern)
- [Context API](#context-api)
---
## Event Handler Syntax
Svelte 5 replaces `on:click` directive syntax with standard HTML attribute syntax `onclick`.
### Basic Event Handlers
**Svelte 4:**
```svelte
<button on:click={handleClick}>Click</button>
<input on:input={handleInput} />
<form on:submit={handleSubmit}>...</form>
```
**Svelte 5:**
```svelte
<button onclick={handleClick}>Click</button>
<input oninput={handleInput} />
<form onsubmit={handleSubmit}>...</form>
```
### Event Modifiers Migration
Event modifiers no longer exist. Use wrapper functions:
**Svelte 4:**
```svelte
<form on:submit|preventDefault={handleSubmit}>...</form>
<button on:click|stopPropagation={handleClick}>...</button>
```
**Svelte 5:**
```svelte
<script>
function handleSubmit(event) {
event.preventDefault();
// ... handle form
}
function handleClick(event) {
event.stopPropagation();
// ... handle click
}
</script>
<form onsubmit={handleSubmit}>...</form>
<button onclick={handleClick}>...</button>
```
### Capture, Passive, and NonPassive
```svelte
<!-- Capture phase -->
<div onclickcapture={handleCapture}>
<button onclick={handleClick}>Click</button>
</div>
<!-- Passive listener -->
<div ontouchstartpassive={handleTouch}>...</div>
<!-- Non-passive -->
<div ontouchmovenonpassive={(e) => e.preventDefault()}>...</div>
```
### Inline Handlers
```svelte
<button onclick={() => count++}>Count: {count}</button>
<input oninput={(e) => name = e.target.value} />
```
### Event Handler Shorthand
```svelte
<script>
function onclick(event) {
console.log('Clicked!', event);
}
</script>
<button {onclick}>Click</button>
```
### Spreading Event Handlers
```svelte
<script>
let handlers = {
onclick: () => console.log('clicked'),
onmouseenter: () => console.log('entered'),
onmouseleave: () => console.log('left')
};
</script>
<button {...handlers}>Hover or Click</button>
```
### Multiple Handlers for Same Event
In Svelte 5, combine logic into one handler:
```svelte
<script>
function handleClick(event) {
handler1(event);
handler2(event);
}
</script>
<button onclick={handleClick}>...</button>
```
### TypeScript Event Typing
```svelte
<script lang="ts">
function handleClick(event: MouseEvent) {
console.log(event.clientX, event.clientY);
}
function handleInput(event: Event) {
const target = event.target as HTMLInputElement;
console.log(target.value);
}
function handleSubmit(event: SubmitEvent) {
event.preventDefault();
const formData = new FormData(event.currentTarget as HTMLFormElement);
}
</script>
```
---
## Callback Props Pattern
Svelte 5 deprecates `createEventDispatcher` in favor of callback props for component-to-parent communication.
### Basic Event Pattern
**Svelte 4:**
```svelte
<!-- Button.svelte -->
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
</script>
<button on:click={() => dispatch('click', { timestamp: Date.now() })}>Click</button>
<!-- Parent.svelte -->
<Button on:click={(e) => console.log(e.detail)} />
```
**Svelte 5:**
```svelte
<!-- Button.svelte -->
<script>
let { onclick } = $props();
</script>
<button onclick={() => onclick?.({ timestamp: Date.now() })}>Click</button>
<!-- Parent.svelte -->
<Button onclick={(data) => console.log(data)} />
```
### Multiple Callbacks
```svelte
<!-- Dialog.svelte -->
<script>
let { onconfirm, oncancel, onclose } = $props();
</script>
<dialog>
<button onclick={() => onconfirm?.()}>Confirm</button>
<button onclick={() => oncancel?.()}>Cancel</button>
<button onclick={() => onclose?.()}>X</button>
</dialog>
<!-- Parent.svelte -->
<Dialog
onconfirm={() => save()}
oncancel={() => reset()}
onclose={() => visible = false}
/>
```
### Typed Callbacks with TypeScript
```svelte
<script lang="ts">
interface Props {
value?: string;
onsearch?: (query: string) => void;
onchange?: (value: string) => void;
onclear?: () => void;
}
let { value = '', onsearch, onchange, onclear }: Props = $props();
function handleInput(e: Event) {
const newValue = (e.target as HTMLInputElement).value;
value = newValue;
onchange?.(newValue);
}
</script>
```
### Forwarding Native Events
```svelte
<script lang="ts">
import type { MouseEventHandler } from 'svelte/elements';
interface Props {
onclick?: MouseEventHandler<HTMLButtonElement>;
children: import('svelte').Snippet;
}
let { onclick, children, ...rest }: Props = $props();
</script>
<button {onclick} {...rest}>{@render children()}</button>
```
---
## Context API
Context functions must be called synchronously during component initialization.
### Correct Context Usage
```svelte
<script>
import { setContext, getContext } from 'svelte';
// These run during initialization - CORRECT
setContext('theme', 'dark');
const theme = getContext('theme');
// INCORRECT - in $effect or callback
// $effect(() => { setContext('theme', 'dark'); }); // ERROR
</script>
```
### Reactive Context Values
Pass `$state` objects for reactive context:
```svelte
<!-- Provider.svelte -->
<script>
import { setContext } from 'svelte';
let theme = $state('light');
setContext('theme', {
get current() { return theme; },
toggle() { theme = theme === 'light' ? 'dark' : 'light'; }
});
let { children } = $props();
</script>
{@render children()}
<!-- Consumer.svelte -->
<script>
import { getContext } from 'svelte';
const themeContext = getContext('theme');
</script>
<p>Theme: {themeContext.current}</p>
<button onclick={themeContext.toggle}>Toggle</button>
```
### Type-Safe Context Pattern
```ts
// context.ts
import { setContext, getContext } from 'svelte';
const THEME_KEY = Symbol('theme');
interface ThemeContext {
current: 'light' | 'dark';
toggle: () => void;
}
export function setThemeContext(context: ThemeContext) {
setContext(THEME_KEY, context);
}
export function getThemeContext(): ThemeContext {
const context = getContext<ThemeContext>(THEME_KEY);
if (!context) throw new Error('Theme context not found');
return context;
}
```
### Avoid Generic Keys
```svelte
<script>
// Option 1: Symbol
const USER_KEY = Symbol('user');
setContext(USER_KEY, userData);
// Option 2: Unique string
setContext('myapp:user', userData);
// Option 3: Object key
const userKey = {};
setContext(userKey, userData);
</script>
```
### hasContext Check
```svelte
<script>
import { hasContext, getContext } from 'svelte';
const hasTheme = hasContext('theme');
const theme = hasTheme ? getContext('theme') : { current: 'light' };
</script>
```
### Context Boundaries
Context flows down the component tree:
```svelte
<!-- Parent.svelte -->
<script>
import { getContext, setContext } from 'svelte';
const level = getContext('level'); // 0
setContext('level', level + 1); // Override for children
</script>
<Child /> <!-- Will see level = 1 -->
```
@@ -0,0 +1,339 @@
# Svelte 4 to Svelte 5 Migration Reference
## Table of Contents
- [Migrating Reactive Statements](#migrating-reactive-statements)
- [Migrating Stores to Runes](#migrating-stores-to-runes)
---
## Migrating Reactive Statements
Svelte 5 replaces `$:` with `$derived` (for values) and `$effect` (for side effects).
### Computed Values: $: to $derived
**Svelte 4:**
```svelte
<script>
let count = 0;
$: doubled = count * 2;
$: quadrupled = doubled * 2;
</script>
```
**Svelte 5:**
```svelte
<script>
let count = $state(0);
let doubled = $derived(count * 2);
let quadrupled = $derived(doubled * 2);
</script>
```
### Complex Computed Values: $derived.by
**Svelte 4:**
```svelte
<script>
let items = [];
let filter = 'all';
$: filteredItems = {
if (filter === 'all') return items;
if (filter === 'active') return items.filter(i => !i.done);
return items.filter(i => i.done);
};
</script>
```
**Svelte 5:**
```svelte
<script>
let items = $state([]);
let filter = $state('all');
let filteredItems = $derived.by(() => {
if (filter === 'all') return items;
if (filter === 'active') return items.filter(i => !i.done);
return items.filter(i => i.done);
});
</script>
```
### Side Effects: $: to $effect
**Svelte 4:**
```svelte
<script>
let count = 0;
$: console.log('Count changed:', count);
$: document.title = `Count: ${count}`;
</script>
```
**Svelte 5:**
```svelte
<script>
let count = $state(0);
$effect(() => { console.log('Count changed:', count); });
$effect(() => { document.title = `Count: ${count}`; });
</script>
```
### Conditional Side Effects
**Svelte 4:**
```svelte
<script>
let value;
$: if (value > 100) { alert('Value too high!'); }
</script>
```
**Svelte 5:**
```svelte
<script>
let value = $state(0);
$effect(() => {
if (value > 100) { alert('Value too high!'); }
});
</script>
```
### Props Migration
**Svelte 4:**
```svelte
<script>
export let name;
export let count = 0;
$: greeting = `Hello, ${name}!`;
</script>
```
**Svelte 5:**
```svelte
<script>
let { name, count = 0 } = $props();
let greeting = $derived(`Hello, ${name}!`);
</script>
```
### Effect with Cleanup
**Svelte 4:**
```svelte
<script>
import { onDestroy } from 'svelte';
let count = 0;
let interval;
$: {
clearInterval(interval);
interval = setInterval(() => console.log(count), 1000);
}
onDestroy(() => clearInterval(interval));
</script>
```
**Svelte 5:**
```svelte
<script>
let count = $state(0);
$effect(() => {
const interval = setInterval(() => console.log(count), 1000);
return () => clearInterval(interval); // Cleanup built-in
});
</script>
```
### Migration Cheat Sheet
| Svelte 4 Pattern | Svelte 5 Replacement |
|------------------|----------------------|
| `let x = 0` (in component) | `let x = $state(0)` |
| `export let prop` | `let { prop } = $props()` |
| `$: derived = expr` | `let derived = $derived(expr)` |
| `$: { complex }` (value) | `let x = $derived.by(() => { ... })` |
| `$: console.log(x)` | `$effect(() => console.log(x))` |
| `$: if (x) { ... }` | `$effect(() => { if (x) { ... } })` |
| `$: document.title = x` | `$effect(() => { document.title = x })` |
### Automated Migration
```bash
npx sv migrate svelte-5
```
---
## Migrating Stores to Runes
Svelte 5 runes can replace most store use cases with simpler, more direct reactive state.
### Local Component State
**Svelte 4:**
```svelte
<script>
import { writable } from 'svelte/store';
const count = writable(0);
function increment() { count.update(n => n + 1); }
</script>
<button on:click={increment}>Count: {$count}</button>
```
**Svelte 5:**
```svelte
<script>
let count = $state(0);
function increment() { count++; }
</script>
<button onclick={increment}>Count: {count}</button>
```
### Shared State Across Components
**Svelte 4 (stores.ts):**
```ts
import { writable } from 'svelte/store';
export const user = writable(null);
export const theme = writable('light');
```
**Svelte 5 (state.svelte.ts):**
```ts
export const user = $state<User | null>(null);
export const theme = $state({ current: 'light' as 'light' | 'dark' });
export function setTheme(newTheme: 'light' | 'dark') {
theme.current = newTheme;
}
```
```svelte
<script>
import { theme, setTheme } from './state.svelte';
</script>
<p>Theme: {theme.current}</p>
<button onclick={() => setTheme('dark')}>Dark Mode</button>
```
### Derived Store to $derived
**Svelte 4:**
```ts
import { writable, derived } from 'svelte/store';
export const items = writable([]);
export const completedCount = derived(items, $items =>
$items.filter(i => i.done).length
);
```
**Svelte 5:**
```ts
// state.svelte.ts
export const items = $state<Item[]>([]);
export function getCompletedCount() {
return items.filter(i => i.done).length;
}
```
```svelte
<script>
import { items } from './state.svelte';
let completedCount = $derived(items.filter(i => i.done).length);
</script>
```
### Custom Store to Reactive Class
**Svelte 4:**
```ts
function createCounter() {
const { subscribe, set, update } = writable(0);
return {
subscribe,
increment: () => update(n => n + 1),
decrement: () => update(n => n - 1),
reset: () => set(0)
};
}
export const counter = createCounter();
```
**Svelte 5:**
```ts
// counter.svelte.ts
class Counter {
value = $state(0);
increment() { this.value++; }
decrement() { this.value--; }
reset() { this.value = 0; }
}
export const counter = new Counter();
```
```svelte
<script>
import { counter } from './counter.svelte';
</script>
<p>{counter.value}</p>
<button onclick={() => counter.increment()}>+</button>
```
### Async State Pattern
**Svelte 5:**
```ts
// api.svelte.ts
export const state = $state({
data: null as Data | null,
loading: false,
error: null as Error | null
});
export async function fetchData() {
state.loading = true;
state.error = null;
try {
const res = await fetch('/api/data');
state.data = await res.json();
} catch (e) {
state.error = e as Error;
} finally {
state.loading = false;
}
}
```
### When to Still Use Stores
1. **Library interop** - Libraries that expect Svelte stores
2. **Legacy code** - Gradual migration
3. **Observable patterns** - When you need the subscribe API
4. **Server-side rendering** - Stores have built-in SSR handling
### Store to Rune Cheat Sheet
| Store Pattern | Rune Replacement |
|---------------|------------------|
| `writable(value)` | `$state(value)` |
| `$store` (auto-subscribe) | Direct access to `$state` value |
| `store.set(x)` | `state = x` |
| `store.update(fn)` | Direct mutation or reassignment |
| `derived(store, fn)` | `$derived(fn())` in component |
| `readable(value, start)` | `$state` + `$effect` for setup |
@@ -0,0 +1,443 @@
# Svelte 5 Performance Reference
## Table of Contents
- [Universal Reactivity](#universal-reactivity)
- [Avoiding Over-Reactivity](#avoiding-over-reactivity)
- [Preventing Load Waterfalls](#preventing-load-waterfalls)
- [Streaming Non-Critical Data](#streaming-non-critical-data)
- [Component Testing](#component-testing)
---
## Universal Reactivity
Svelte 5 allows runes in `.svelte.js` or `.svelte.ts` files for shared state outside components.
### Shared Counter State
```ts
// counter.svelte.ts
export const counter = $state({ count: 0 });
export function increment() { counter.count++; }
export function decrement() { counter.count--; }
export function reset() { counter.count = 0; }
```
```svelte
<script>
import { counter, increment } from './counter.svelte';
</script>
<p>Count: {counter.count}</p>
<button onclick={increment}>+</button>
```
### Object State with Getters
```ts
// user.svelte.ts
const state = $state<User>({ firstName: '', lastName: '', email: '' });
export const user = {
get firstName() { return state.firstName; },
set firstName(v: string) { state.firstName = v; },
get fullName() { return `${state.firstName} ${state.lastName}`; },
get isValid() { return state.firstName && state.email.includes('@'); }
};
```
### Reactive Class Pattern
```ts
// todo.svelte.ts
class TodoStore {
items = $state<Todo[]>([]);
filter = $state<'all' | 'active' | 'completed'>('all');
get filtered() {
switch (this.filter) {
case 'active': return this.items.filter(t => !t.done);
case 'completed': return this.items.filter(t => t.done);
default: return this.items;
}
}
add(text: string) {
this.items.push({ id: Date.now(), text, done: false });
}
}
export const todos = new TodoStore();
```
### Important Notes
1. **File extension matters**: Must use `.svelte.js` or `.svelte.ts`
2. **No $derived in module scope**: Use getters for derived values
3. **SSR caution**: Avoid initializing browser-only state at module level
---
## Avoiding Over-Reactivity
Common mistakes with runes can cause unnecessary re-renders, infinite loops, or degraded performance.
### Anti-Pattern 1: Using $effect to Set Derived Values
**WRONG:**
```svelte
<script>
let count = $state(0);
let doubled = $state(0);
$effect(() => { doubled = count * 2; }); // Anti-pattern!
</script>
```
**CORRECT:**
```svelte
<script>
let count = $state(0);
let doubled = $derived(count * 2);
</script>
```
### Anti-Pattern 2: Circular Dependencies
**WRONG:**
```svelte
<script>
let a = $state(1);
let b = $state(2);
$effect(() => {
if (a > 10) b = 0;
if (b > 10) a = 0; // Triggers the effect again!
});
</script>
```
**CORRECT: Use separate effects or event handlers**
### Anti-Pattern 3: Not Using untrack
**WRONG:**
```svelte
<script>
let count = $state(0);
let log = $state([]);
$effect(() => {
log.push(`Count is ${count}`); // log change triggers re-run!
});
</script>
```
**CORRECT:**
```svelte
<script>
import { untrack } from 'svelte';
let count = $state(0);
let log = $state([]);
$effect(() => {
untrack(() => { log.push(`Count is ${count}`); });
});
</script>
```
### Anti-Pattern 4: Heavy Computations in $derived
**WRONG:**
```svelte
<script>
let items = $state([/* thousands */]);
let filter = $state('');
let filtered = $derived(
items.filter(item => JSON.stringify(item).includes(filter))
);
</script>
```
**CORRECT: Debounce expensive computations**
```svelte
<script>
let filter = $state('');
let debouncedFilter = $state('');
$effect(() => {
const timeout = setTimeout(() => { debouncedFilter = filter; }, 300);
return () => clearTimeout(timeout);
});
let filtered = $derived(
items.filter(item => item.name.includes(debouncedFilter))
);
</script>
```
### Anti-Pattern 5: Effect for DOM Manipulation
**WRONG:**
```svelte
<script>
let visible = $state(false);
let element;
$effect(() => {
if (element) element.style.display = visible ? 'block' : 'none';
});
</script>
```
**CORRECT:**
```svelte
{#if visible}<div>Content</div>{/if}
<!-- Or -->
<div class:hidden={!visible}>Content</div>
```
### Performance Tips
1. Use `$derived` over `$effect` for computed values
2. Debounce expensive reactive computations
3. Use `untrack` to prevent unnecessary dependencies
4. Group related state into objects
5. Let Svelte handle DOM updates
---
## Preventing Load Waterfalls
Sequential API calls multiply latency. Use parallel requests and streaming.
### Anti-Pattern: Waterfall
**WRONG (3 seconds total):**
```ts
export const load = async ({ fetch }) => {
const user = await fetch('/api/user').then(r => r.json()); // 1s
const posts = await fetch(`/api/users/${user.id}/posts`).then(r => r.json()); // 1s
const comments = await fetch('/api/comments').then(r => r.json()); // 1s
return { user, posts, comments };
};
```
### Pattern 1: Parallel with Promise.all
**CORRECT (1 second total):**
```ts
export const load = async ({ fetch }) => {
const [user, posts, comments] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
return { user, posts, comments };
};
```
### Pattern 2: Partial Dependencies
```ts
export const load = async ({ fetch }) => {
const user = await fetch('/api/user').then(r => r.json());
const [posts, followers] = await Promise.all([
fetch(`/api/users/${user.id}/posts`).then(r => r.json()),
fetch(`/api/users/${user.id}/followers`).then(r => r.json())
]);
return { user, posts, followers };
};
```
### Pattern 3: Streaming Non-Critical Data
```ts
export const load = async ({ fetch }) => {
const user = await fetch('/api/user').then(r => r.json());
return {
user,
recommendations: fetch('/api/recommendations').then(r => r.json()),
analytics: fetch('/api/analytics').then(r => r.json())
};
};
```
### Performance Comparison
| Pattern | 3 APIs x 1s each |
|---------|------------------|
| Sequential | 3 seconds |
| Parallel (Promise.all) | 1 second |
| Streaming | 0s initial, streams rest |
---
## Streaming Non-Critical Data
Return promises from load functions to stream non-essential data after initial page render.
### Basic Streaming
**WRONG (blocks on slow data):**
```ts
export const load = async ({ fetch }) => {
const user = await fetch('/api/user').then(r => r.json()); // 100ms
const analytics = await fetch('/api/analytics').then(r => r.json()); // 2000ms
return { user, analytics }; // Page blocked for 2.1 seconds
};
```
**CORRECT (stream slow data):**
```ts
export const load = async ({ fetch }) => {
const user = await fetch('/api/user').then(r => r.json());
const analytics = fetch('/api/analytics').then(r => r.json()); // Don't await
return {
user, // Available in 100ms
analytics // Streams when ready
};
};
```
### Component Handling
```svelte
<script lang="ts">
import type { PageProps } from './$types';
let { data }: PageProps = $props();
</script>
<header><h1>Welcome, {data.user.name}</h1></header>
<aside>
{#await data.analytics}
<div class="skeleton">Loading analytics...</div>
{:then analytics}
<div class="analytics">
<p>Page views: {analytics.views}</p>
</div>
{:catch}
<div class="error">Failed to load analytics</div>
{/await}
</aside>
```
### When to Stream
| Data Type | Stream? | Reason |
|-----------|---------|--------|
| User info | No | Critical for layout |
| Main content | No | Users came for this |
| Analytics | Yes | Not user-facing |
| Recommendations | Yes | Supplementary |
| Comments | Maybe | Important but can load later |
---
## Component Testing
Svelte 5 components with runes require proper Vitest configuration.
### Option 1: Vitest Browser Mode (Recommended)
```ts
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import { svelte } from '@sveltejs/vite-plugin-svelte';
export default defineConfig({
plugins: [svelte()],
test: {
browser: {
enabled: true,
provider: 'playwright',
name: 'chromium'
}
}
});
```
### Option 2: JSDOM with @testing-library/svelte
```ts
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import { svelte } from '@sveltejs/vite-plugin-svelte';
export default defineConfig({
plugins: [svelte({ hot: !process.env.VITEST })],
test: {
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,ts}'],
globals: true
}
});
```
### Basic Component Test
```ts
import { render, screen, fireEvent } from '@testing-library/svelte';
import { expect, test } from 'vitest';
import Counter from './Counter.svelte';
test('increments count when clicked', async () => {
render(Counter);
const button = screen.getByRole('button');
expect(button).toHaveTextContent('Count: 0');
await fireEvent.click(button);
expect(button).toHaveTextContent('Count: 1');
});
```
### Testing Props
```ts
test('renders with custom name', () => {
render(Greeting, { props: { name: 'Alice' } });
expect(screen.getByText('Hello, Alice!')).toBeInTheDocument();
});
```
### Testing Callbacks
```ts
import { vi } from 'vitest';
test('calls onclick callback when clicked', async () => {
const handleClick = vi.fn();
render(Button, { props: { onclick: handleClick } });
await fireEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
```
### Testing Async Components
```ts
import { waitFor } from '@testing-library/svelte';
test('loads and displays user data', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ name: 'Alice' })
});
render(UserProfile, { props: { userId: '123' } });
expect(screen.getByText('Loading...')).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByText('Alice')).toBeInTheDocument();
});
});
```
@@ -0,0 +1,394 @@
# Svelte 5 Runes Reference
## Table of Contents
- [$state - Reactive State](#state---reactive-state)
- [$derived - Computed Values](#derived---computed-values)
- [$effect - Side Effects](#effect---side-effects)
- [$props - Component Props](#props---component-props)
- [$bindable - Two-Way Binding](#bindable---two-way-binding)
- [$inspect - Debugging](#inspect---debugging)
---
## $state - Reactive State
In Svelte 5, plain `let` declarations are no longer automatically reactive. Use `$state()` for reactive state.
### Basic Usage
```svelte
<script>
let count = $state(0);
</script>
<button onclick={() => count++}>
Clicks: {count}
</button>
```
### Object and Array State
Objects and arrays are deeply reactive by default:
```svelte
<script>
let user = $state({ name: 'Alice', age: 30 });
let items = $state(['apple', 'banana']);
</script>
<button onclick={() => user.age++}>Age: {user.age}</button>
<button onclick={() => items.push('cherry')}>Items: {items.length}</button>
```
### Class State
```svelte
<script>
class Counter {
count = $state(0);
increment() { this.count++; }
}
const counter = new Counter();
</script>
<button onclick={() => counter.increment()}>{counter.count}</button>
```
### Raw State (Opt-out of Deep Reactivity)
```svelte
<script>
let items = $state.raw([1, 2, 3]);
// This WON'T trigger an update:
items.push(4);
// This WILL trigger an update:
items = [...items, 4];
</script>
```
---
## $derived - Computed Values
Replaces `$:` reactive statements for computed values.
### Basic Usage
```svelte
<script>
let count = $state(0);
let doubled = $derived(count * 2);
let quadrupled = $derived(doubled * 2);
</script>
```
### Complex Derivations with $derived.by
```svelte
<script>
let items = $state([1, 2, 3, 4, 5]);
let filter = $state('even');
let filteredItems = $derived.by(() => {
if (filter === 'even') return items.filter(n => n % 2 === 0);
return items.filter(n => n % 2 !== 0);
});
</script>
```
### Deriving from Props
```svelte
<script>
let { firstName, lastName } = $props();
let fullName = $derived(`${firstName} ${lastName}`);
</script>
```
### $derived vs $effect
Use `$derived` for computing values, `$effect` for side effects:
```svelte
<script>
let count = $state(0);
// CORRECT: Use $derived for computed values
let doubled = $derived(count * 2);
// INCORRECT: Don't use $effect to set derived values
// let doubled;
// $effect(() => { doubled = count * 2; }); // Anti-pattern!
</script>
```
---
## $effect - Side Effects
Runs code when component mounts and when dependencies change. Requires cleanup for subscriptions.
### Basic Effect with Cleanup
```svelte
<script>
let count = $state(0);
$effect(() => {
const interval = setInterval(() => count++, 1000);
return () => clearInterval(interval); // Cleanup
});
</script>
```
### DOM Event Listeners
```svelte
<script>
let mouseX = $state(0);
let mouseY = $state(0);
$effect(() => {
function handleMouseMove(event) {
mouseX = event.clientX;
mouseY = event.clientY;
}
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
});
</script>
```
### External Subscriptions
```svelte
<script>
let { userId } = $props();
let userData = $state(null);
$effect(() => {
const unsubscribe = database.subscribe(`users/${userId}`, (data) => {
userData = data;
});
return () => unsubscribe();
});
</script>
```
### $effect.pre for Pre-DOM Updates
```svelte
<script>
let div;
let messages = $state([]);
$effect.pre(() => {
if (div) {
const shouldScroll = div.scrollTop + div.clientHeight >= div.scrollHeight - 20;
if (shouldScroll) {
tick().then(() => { div.scrollTop = div.scrollHeight; });
}
}
});
</script>
```
### Untracked Dependencies
```svelte
<script>
import { untrack } from 'svelte';
let count = $state(0);
let logCount = $state(0);
$effect(() => {
// Only runs when count changes, not logCount
console.log(count, untrack(() => logCount));
});
</script>
```
---
## $props - Component Props
Replaces `export let` for declaring component props.
### Basic Usage
```svelte
<script>
let { name, count = 0, disabled = false } = $props();
</script>
```
### Rest Props Pattern
```svelte
<script>
let { class: className, children, ...restProps } = $props();
</script>
<div class={className} {...restProps}>
{@render children?.()}
</div>
```
### Renaming Reserved Words
```svelte
<script>
let {
class: className, // 'class' is reserved
for: htmlFor, // 'for' is reserved
...rest
} = $props();
</script>
```
### TypeScript Props
```svelte
<script lang="ts">
interface Props {
name: string;
count?: number;
onClick?: (event: MouseEvent) => void;
}
let { name, count = 0, onClick }: Props = $props();
</script>
```
### Props with Children
```svelte
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
title: string;
children: Snippet;
footer?: Snippet;
}
let { title, children, footer }: Props = $props();
</script>
<article>
<h1>{title}</h1>
<main>{@render children()}</main>
{#if footer}<footer>{@render footer()}</footer>{/if}
</article>
```
---
## $bindable - Two-Way Binding
Props must be explicitly marked with `$bindable()` to support `bind:`.
### Basic Usage
```svelte
<!-- Input.svelte -->
<script>
let { value = $bindable() } = $props();
</script>
<input bind:value />
<!-- Parent.svelte -->
<script>
import Input from './Input.svelte';
let name = $state('');
</script>
<Input bind:value={name} />
```
### Bindable with Default Value
```svelte
<script>
let { value = $bindable('default') } = $props();
</script>
```
### Multiple Bindable Props
```svelte
<script>
let {
value = $bindable(''),
checked = $bindable(false),
selected = $bindable(null)
} = $props();
</script>
```
### TypeScript with Bindable
```svelte
<script lang="ts">
interface Props {
value: string;
disabled?: boolean;
}
let { value = $bindable(''), disabled = false }: Props = $props();
</script>
```
---
## $inspect - Debugging
Development-only debugging that logs values when they change. Stripped in production.
### Basic Usage
```svelte
<script>
let count = $state(0);
$inspect(count); // Logs every time count changes
</script>
```
### Multiple Values
```svelte
<script>
let name = $state('Alice');
let age = $state(30);
$inspect(name, age);
</script>
```
### Custom Logging with $inspect.with
```svelte
<script>
let user = $state({ name: 'Alice', age: 30 });
$inspect.with((type, ...values) => {
if (type === 'init') console.log('Initial value:', values);
else console.log('Updated to:', values);
}, user);
</script>
```
### Debugging with Breakpoints
```svelte
<script>
let data = $state({ x: 0, y: 0 });
$inspect.with((type, value) => {
if (type === 'update' && value.x > 100) debugger;
}, data);
</script>
```
@@ -0,0 +1,291 @@
# Svelte 5 Snippets Reference
## Table of Contents
- [Replacing Slots with Snippets](#replacing-slots-with-snippets)
- [Rendering with @render](#rendering-with-render)
---
## Replacing Slots with Snippets
Svelte 5 replaces `<slot>` with snippets - a more powerful and type-safe composition primitive.
### Default Content (children)
**Svelte 4:**
```svelte
<div class="card"><slot /></div>
```
**Svelte 5:**
```svelte
<script>
let { children } = $props();
</script>
<div class="card">{@render children?.()}</div>
```
### Named Slots to Named Snippets
**Svelte 4:**
```svelte
<header><slot name="header" /></header>
<main><slot /></main>
<footer><slot name="footer" /></footer>
<!-- Usage -->
<Layout>
<h1 slot="header">Title</h1>
<p>Content</p>
<span slot="footer">Footer</span>
</Layout>
```
**Svelte 5:**
```svelte
<script>
let { header, children, footer } = $props();
</script>
<header>{@render header?.()}</header>
<main>{@render children?.()}</main>
<footer>{@render footer?.()}</footer>
<!-- Usage -->
<Layout>
{#snippet header()}<h1>Title</h1>{/snippet}
<p>Content</p>
{#snippet footer()}<span>Footer</span>{/snippet}
</Layout>
```
### Slot Props to Snippet Parameters
**Svelte 4:**
```svelte
<ul>
{#each items as item}
<li><slot {item} /></li>
{/each}
</ul>
<List {items} let:item>
<span>{item.name}</span>
</List>
```
**Svelte 5:**
```svelte
<script>
let { items, children } = $props();
</script>
<ul>
{#each items as item}
<li>{@render children?.(item)}</li>
{/each}
</ul>
<List {items}>
{#snippet children(item)}
<span>{item.name}</span>
{/snippet}
</List>
```
### Slot Fallback to Snippet Fallback
```svelte
<script>
let { children } = $props();
</script>
{#if children}
{@render children()}
{:else}
<p>Default content</p>
{/if}
```
### Defining Snippets Within Components
```svelte
<script>
let items = $state([
{ id: 1, name: 'Item 1', type: 'normal' },
{ id: 2, name: 'Item 2', type: 'featured' }
]);
</script>
{#snippet normalItem(item)}
<li>{item.name}</li>
{/snippet}
{#snippet featuredItem(item)}
<li class="featured">*** {item.name} ***</li>
{/snippet}
<ul>
{#each items as item}
{#if item.type === 'featured'}
{@render featuredItem(item)}
{:else}
{@render normalItem(item)}
{/if}
{/each}
</ul>
```
### TypeScript Typing
```svelte
<script lang="ts">
import type { Snippet } from 'svelte';
interface Item { id: number; name: string; }
interface Props {
header?: Snippet;
children: Snippet<[item: Item]>;
footer?: Snippet;
}
let { header, children, footer }: Props = $props();
</script>
```
---
## Rendering with @render
Snippets must be rendered using `{@render}`. Optional snippets need null-safe calling.
### Basic Snippet Rendering
```svelte
<script>
let { children } = $props();
</script>
<div>{@render children()}</div>
```
### Optional Snippets
Always use optional chaining for snippets that might not be provided:
```svelte
<script>
let { children } = $props();
</script>
{@render children?.()}
```
### Conditional Rendering
```svelte
<script>
let { header, children, footer } = $props();
</script>
{#if header}
<header>{@render header()}</header>
{/if}
<main>{@render children?.()}</main>
{#if footer}
<footer>{@render footer()}</footer>
{/if}
```
### Rendering with Arguments
```svelte
<script>
let { items, itemTemplate } = $props();
</script>
<ul>
{#each items as item, index}
<li>{@render itemTemplate(item, index)}</li>
{/each}
</ul>
<!-- Usage -->
<List {items}>
{#snippet itemTemplate(item, index)}
<span>{index + 1}. {item.name}</span>
{/snippet}
</List>
```
### Rendering Multiple Times
Snippets can be rendered multiple times:
```svelte
<script>
let { icon } = $props();
</script>
<button>
{@render icon?.()}
<span>Click me</span>
{@render icon?.()}
</button>
```
### Rendering Dynamic Snippets
```svelte
<script>
let { normalView, editView, isEditing } = $props();
let currentView = $derived(isEditing ? editView : normalView);
</script>
{@render currentView?.()}
```
### TypeScript with @render
```svelte
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
children: Snippet;
header?: Snippet;
row: Snippet<[data: { id: number; name: string }]>;
}
let { children, header, row }: Props = $props();
</script>
{@render header?.()}
{@render children()}
{@render row({ id: 1, name: 'Test' })}
```
### Passing Snippets to Child Components
```svelte
<!-- Wrapper.svelte -->
<script>
import Inner from './Inner.svelte';
let { itemRenderer } = $props();
</script>
<Inner {itemRenderer} />
<!-- Inner.svelte -->
<script>
let { itemRenderer } = $props();
</script>
{#each items as item}
{@render itemRenderer?.(item)}
{/each}
```
@@ -0,0 +1,401 @@
# SvelteKit Patterns Reference
## Table of Contents
- [Load Function Types](#load-function-types)
- [Page Props Typing](#page-props-typing)
- [Form Actions Error Handling](#form-actions-error-handling)
- [SSR State Isolation](#ssr-state-isolation)
---
## Load Function Types
SvelteKit has two load function types: universal (`+page.js`) and server-only (`+page.server.ts`).
### When to Use Each
| Use +page.server.ts | Use +page.js |
|---------------------|--------------|
| Accessing secrets/credentials | Public APIs only |
| Database connections | Non-serializable data (functions, classes) |
| Server-only APIs | Client-side caching benefits |
| Sensitive business logic | |
### Security: Secrets in Server Load
**WRONG (secrets exposed):**
```ts
// +page.js - DANGEROUS: runs in browser!
export const load = async ({ fetch }) => {
const response = await fetch('https://api.stripe.com/charges', {
headers: { 'Authorization': `Bearer ${STRIPE_SECRET_KEY}` } // EXPOSED!
});
return { charges: await response.json() };
};
```
**CORRECT:**
```ts
// +page.server.ts - only runs on server
import { STRIPE_SECRET_KEY } from '$env/static/private';
export const load = async ({ fetch }) => {
const response = await fetch('https://api.stripe.com/charges', {
headers: { 'Authorization': `Bearer ${STRIPE_SECRET_KEY}` }
});
return { charges: await response.json() };
};
```
### Serialization: Non-serializable in Universal Load
**WRONG (server load can't serialize functions):**
```ts
// +page.server.ts
export const load = async () => {
return {
formatDate: (date: Date) => date.toLocaleDateString(), // ERROR
parser: new DOMParser() // ERROR
};
};
```
**CORRECT:**
```ts
// +page.js
export const load = async () => {
return {
formatDate: (date: Date) => date.toLocaleDateString(), // OK
parser: typeof window !== 'undefined' ? new DOMParser() : null
};
};
```
### Database Access
```ts
// +page.server.ts - CORRECT
import { db } from '$lib/server/database';
export const load = async () => {
const users = await db.query('SELECT * FROM users');
return { users };
};
```
### Environment Variables
```ts
// +page.server.ts - Private env vars
import { DATABASE_URL, API_SECRET } from '$env/static/private';
// +page.js - Only public env vars
import { PUBLIC_API_URL } from '$env/static/public';
```
---
## Page Props Typing
SvelteKit generates types in `./$types` for full type safety with `$props()`.
### Basic Page Data Typing
```svelte
<!-- +page.svelte -->
<script lang="ts">
import type { PageProps } from './$types';
let { data }: PageProps = $props();
</script>
<h1>{data.title}</h1>
```
### Page Data with Form Actions
```svelte
<script lang="ts">
import type { PageProps } from './$types';
let { data, form }: PageProps = $props();
</script>
{#if form?.success}
<p class="success">{form.message}</p>
{/if}
{#if form?.error}
<p class="error">{form.error}</p>
{/if}
```
### Layout Data Typing
```svelte
<!-- +layout.svelte -->
<script lang="ts">
import type { LayoutProps } from './$types';
let { data, children }: LayoutProps = $props();
</script>
<nav>
{#if data.user}<span>Welcome, {data.user.name}</span>{/if}
</nav>
{@render children()}
```
### Corresponding Load Function
```ts
// +page.server.ts
import type { PageServerLoad, Actions } from './$types';
export const load: PageServerLoad = async () => {
return {
title: 'My Page',
items: await fetchItems()
};
};
export const actions: Actions = {
default: async ({ request }) => {
return { success: true, message: 'Saved successfully' };
}
};
```
### Error Page
```svelte
<!-- +error.svelte -->
<script lang="ts">
import { page } from '$app/state';
</script>
<h1>{page.status}: {page.error?.message}</h1>
```
---
## Form Actions Error Handling
SvelteKit distinguishes between validation errors (`fail()`) and unexpected errors (`throw error()`).
### Use fail() for Validation Errors
**WRONG:**
```ts
import { error } from '@sveltejs/kit';
export const actions = {
default: async ({ request }) => {
if (!email?.toString().includes('@')) {
throw error(400, 'Invalid email'); // Shows error page!
}
}
};
```
**CORRECT:**
```ts
import { fail } from '@sveltejs/kit';
export const actions = {
default: async ({ request }) => {
const data = await request.formData();
const email = data.get('email')?.toString() ?? '';
if (!email.includes('@')) {
return fail(400, {
error: 'Invalid email address',
email // Return for repopulation
});
}
return { success: true };
}
};
```
### Use throw error() for Unexpected Errors
```ts
import { fail, error } from '@sveltejs/kit';
export const actions = {
default: async ({ request }) => {
const data = await request.formData();
// Validation - use fail()
if (!data.get('title')) {
return fail(400, { error: 'Title is required' });
}
try {
await database.save(data);
return { success: true };
} catch (e) {
// Unexpected error - use throw
throw error(500, 'Unable to save. Please try again later.');
}
}
};
```
### Structured Validation Responses
```ts
interface FormErrors {
email?: string;
password?: string;
}
export const actions = {
register: async ({ request }) => {
const data = await request.formData();
const errors: FormErrors = {};
if (!email) errors.email = 'Email is required';
if (password.length < 8) errors.password = 'Password must be at least 8 characters';
if (Object.keys(errors).length > 0) {
return fail(400, { errors, values: { email } });
}
return { success: true };
}
};
```
### Handle in Component
```svelte
<script lang="ts">
import type { PageProps } from './$types';
import { enhance } from '$app/forms';
let { form }: PageProps = $props();
</script>
<form method="POST" action="?/register" use:enhance>
<label>
Email
<input name="email" value={form?.values?.email ?? ''} />
{#if form?.errors?.email}
<span class="error">{form.errors.email}</span>
{/if}
</label>
<button type="submit">Register</button>
</form>
```
### Error Response Summary
| Situation | Function | Result |
|-----------|----------|--------|
| Missing field | `fail(400, {...})` | Form state preserved |
| Invalid format | `fail(400, {...})` | Form state preserved |
| Not found | `throw error(404, ...)` | Error page |
| Server crash | `throw error(500, ...)` | Error page |
| Auth required | `throw redirect(303, ...)` | Redirect |
---
## SSR State Isolation
Shared server state persists across requests, potentially leaking data between users.
### Dangerous: Module-Level State
**WRONG:**
```ts
// +page.server.ts
let currentUser = null; // SHARED ACROSS ALL REQUESTS!
export const load = async ({ locals }) => {
currentUser = locals.user; // User B overwrites User A
return { user: currentUser };
};
```
**CORRECT:**
```ts
export const load = async ({ locals }) => {
return { user: locals.user }; // Each request gets its own locals
};
```
### Dangerous: Global Stores
**WRONG:**
```ts
// stores.svelte.ts
export const user = $state<User | null>(null); // Server-side singleton!
```
**CORRECT: Use locals and return data from load**
```ts
// +layout.server.ts
export const load = async ({ locals }) => {
return { user: locals.user };
};
```
### Safe Patterns
**Pattern 1: Use locals**
```ts
// hooks.server.ts
export const handle = async ({ event, resolve }) => {
event.locals.user = await authenticate(event);
event.locals.requestId = crypto.randomUUID();
return resolve(event);
};
// +page.server.ts
export const load = async ({ locals }) => {
return { user: locals.user };
};
```
**Pattern 2: Context for Component Trees**
```svelte
<!-- +layout.svelte -->
<script lang="ts">
import { setContext } from 'svelte';
import type { LayoutProps } from './$types';
let { data, children }: LayoutProps = $props();
setContext('user', {
get current() { return data.user; }
});
</script>
{@render children()}
```
**Pattern 3: Client-Only State**
```ts
// stores.svelte.ts
import { browser } from '$app/environment';
function createClientStore() {
if (!browser) return { value: null };
const state = $state({ value: null });
return state;
}
export const clientState = createClientStore();
```
### SSR Safety Checklist
- [ ] No module-level `let` variables that store user data
- [ ] No global `$state` that gets set during SSR
- [ ] No singleton service classes with mutable state
- [ ] All user-specific data flows through `locals`
- [ ] All page data comes from load function returns
@@ -0,0 +1,305 @@
# Svelte 5 TypeScript Reference
## Table of Contents
- [Props Typing](#props-typing)
- [Generic Components](#generic-components)
---
## Props Typing
`$props()` requires specific patterns for TypeScript typing.
### Basic Props Typing
**Inline typing:**
```svelte
<script lang="ts">
let { name, count = 0 }: { name: string; count?: number } = $props();
</script>
```
**Interface typing (recommended):**
```svelte
<script lang="ts">
interface Props {
name: string;
count?: number;
disabled?: boolean;
}
let { name, count = 0, disabled = false }: Props = $props();
</script>
```
### Children and Snippets Typing
```svelte
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
children: Snippet; // Required children
header?: Snippet; // Optional snippet
row: Snippet<[data: RowData]>; // Snippet with parameter
cell?: Snippet<[value: string, index: number]>; // Multiple params
}
interface RowData { id: number; name: string; }
let { children, header, row, cell }: Props = $props();
</script>
```
### Callback Props Typing
```svelte
<script lang="ts">
interface Props {
value: string;
onchange?: (value: string) => void;
onsubmit?: (data: FormData) => Promise<void>;
onclick?: (event: MouseEvent) => void;
}
let { value, onchange, onsubmit, onclick }: Props = $props();
</script>
```
### Rest Props with HTML Attributes
```svelte
<script lang="ts">
import type { HTMLButtonAttributes } from 'svelte/elements';
interface Props extends HTMLButtonAttributes {
variant?: 'primary' | 'secondary';
loading?: boolean;
}
let { variant = 'primary', loading = false, ...rest }: Props = $props();
</script>
<button class={variant} disabled={loading} {...rest}>
{#if loading}Loading...{:else}{@render children?.()}{/if}
</button>
```
### Input Element Props
```svelte
<script lang="ts">
import type { HTMLInputAttributes } from 'svelte/elements';
interface Props extends Omit<HTMLInputAttributes, 'value'> {
value?: string;
label: string;
error?: string;
}
let { value = $bindable(''), label, error, ...rest }: Props = $props();
</script>
```
### Union Type Props
```svelte
<script lang="ts">
type ButtonVariant = 'primary' | 'secondary' | 'danger';
type ButtonSize = 'sm' | 'md' | 'lg';
interface Props {
variant?: ButtonVariant;
size?: ButtonSize;
children: Snippet;
}
let { variant = 'primary', size = 'md', children }: Props = $props();
</script>
<button class="{variant} {size}">{@render children()}</button>
```
### Discriminated Union Props
```svelte
<script lang="ts">
type Props =
| { type: 'link'; href: string; children: Snippet }
| { type: 'button'; onclick: () => void; children: Snippet };
let props: Props = $props();
</script>
{#if props.type === 'link'}
<a href={props.href}>{@render props.children()}</a>
{:else}
<button onclick={props.onclick}>{@render props.children()}</button>
{/if}
```
---
## Generic Components
Svelte 5 uses the `generics` attribute for type-safe reusable components.
### Basic Generic Component
```svelte
<!-- List.svelte -->
<script lang="ts" generics="T">
interface Props {
items: T[];
children: import('svelte').Snippet<[item: T, index: number]>;
}
let { items, children }: Props = $props();
</script>
<ul>
{#each items as item, index}
<li>{@render children(item, index)}</li>
{/each}
</ul>
<!-- Usage -->
<script lang="ts">
import List from './List.svelte';
interface User { id: number; name: string; }
let users: User[] = $state([{ id: 1, name: 'Alice' }]);
</script>
<List items={users}>
{#snippet children(user, index)}
<span>{index + 1}. {user.name}</span> <!-- Fully typed! -->
{/snippet}
</List>
```
### Multiple Type Parameters
```svelte
<script lang="ts" generics="K, V">
interface Props {
entries: [K, V][];
renderKey: import('svelte').Snippet<[key: K]>;
renderValue: import('svelte').Snippet<[value: V]>;
}
let { entries, renderKey, renderValue }: Props = $props();
</script>
<dl>
{#each entries as [key, value]}
<dt>{@render renderKey(key)}</dt>
<dd>{@render renderValue(value)}</dd>
{/each}
</dl>
```
### Constrained Generics
```svelte
<script lang="ts" generics="T extends { id: string | number }">
interface Props {
items: T[];
selected?: T;
onselect?: (item: T) => void;
children: import('svelte').Snippet<[item: T, isSelected: boolean]>;
}
let { items, selected, onselect, children }: Props = $props();
</script>
{#each items as item}
<div
class:selected={selected?.id === item.id}
onclick={() => onselect?.(item)}
>
{@render children(item, selected?.id === item.id)}
</div>
{/each}
```
### Generic with Default Type
```svelte
<script lang="ts" generics="T = Record<string, unknown>">
interface Props {
data: T[];
columns: (keyof T)[];
}
let { data, columns }: Props = $props();
</script>
```
### Generic Select Component
```svelte
<script lang="ts" generics="T">
interface Props {
options: T[];
value?: T;
getLabel: (option: T) => string;
getValue: (option: T) => string;
onchange?: (selected: T | undefined) => void;
placeholder?: string;
}
let {
options,
value = $bindable(),
getLabel,
getValue,
onchange,
placeholder = 'Select...'
}: Props = $props();
function handleChange(e: Event) {
const selectedValue = (e.target as HTMLSelectElement).value;
const selected = options.find(o => getValue(o) === selectedValue);
value = selected;
onchange?.(selected);
}
</script>
<select onchange={handleChange}>
<option value="">{placeholder}</option>
{#each options as option}
<option
value={getValue(option)}
selected={value && getValue(value) === getValue(option)}
>
{getLabel(option)}
</option>
{/each}
</select>
```
### Generic Async Component
```svelte
<script lang="ts" generics="T">
import type { Snippet } from 'svelte';
interface Props {
promise: Promise<T>;
loading?: Snippet;
error?: Snippet<[error: Error]>;
children: Snippet<[data: T]>;
}
let { promise, loading, error, children }: Props = $props();
</script>
{#await promise}
{#if loading}{@render loading()}{:else}<p>Loading...</p>{/if}
{:then data}
{@render children(data)}
{:catch err}
{#if error}{@render error(err)}{:else}<p>Error: {err.message}</p>{/if}
{/await}
```
@@ -0,0 +1,152 @@
---
name: tailwind-css-patterns
description: Provides comprehensive Tailwind CSS utility-first styling patterns including responsive design, layout utilities, flexbox, grid, spacing, typography, colors, and modern CSS best practices. Use when styling React/Vue/Svelte components, building responsive layouts, implementing design systems, or optimizing CSS workflow.
allowed-tools: Read, Write, Edit, Glob, Grep, Bash
---
# Tailwind CSS Development Patterns
Expert guide for building modern, responsive user interfaces with Tailwind CSS utility-first framework. Covers v4.1+ features including CSS-first configuration, custom utilities, and enhanced developer experience.
## Overview
Provides actionable patterns for responsive, accessible UIs with Tailwind CSS v4.1+. Covers utility composition, dark mode, component patterns, and performance optimization.
## When to Use
- Styling React/Vue/Svelte components
- Building responsive layouts and grids
- Implementing design systems
- Adding dark mode support
- Optimizing CSS workflow
## Quick Reference
### Responsive Breakpoints
| Prefix | Min Width | Description |
|--------|-----------|-------------|
| `sm:` | 640px | Small screens |
| `md:` | 768px | Tablets |
| `lg:` | 1024px | Desktops |
| `xl:` | 1280px | Large screens |
| `2xl:` | 1536px | Extra large |
### Common Patterns
```html
<!-- Center content -->
<div class="flex items-center justify-center min-h-screen">
Content
</div>
<!-- Responsive grid -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<!-- Items -->
</div>
<!-- Card component -->
<div class="bg-white rounded-lg shadow-lg p-6">
<h3 class="text-xl font-bold">Title</h3>
<p class="text-gray-600">Description</p>
</div>
```
## Instructions
1. **Start Mobile-First**: Write base styles for mobile, add responsive prefixes (`sm:`, `md:`, `lg:`) for larger screens
2. **Use Design Tokens**: Leverage Tailwind's spacing, color, and typography scales
3. **Compose Utilities**: Combine multiple utilities for complex styles
4. **Extract Components**: Create reusable component classes for repeated patterns
5. **Configure Theme**: Customize design tokens in `tailwind.config.js` or using `@theme`
6. **Verify Changes**: Test at each breakpoint using DevTools responsive mode. Check for visual regressions and accessibility issues before committing.
## Examples
### Responsive Card Component
```tsx
function ProductCard({ product }: { product: Product }) {
return (
<div className="bg-white rounded-lg shadow-lg overflow-hidden sm:flex">
<img className="h-48 w-full object-cover sm:h-auto sm:w-48" src={product.image} />
<div className="p-6">
<h3 className="text-lg font-semibold">{product.name}</h3>
<button className="mt-4 px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700">
Add to Cart
</button>
</div>
</div>
);
}
```
### Dark Mode Toggle
```html
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
<h1 class="dark:text-white">Title</h1>
</div>
```
### Form Input
```html
<input
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="you@example.com"
/>
```
## Best Practices
1. **Consistent Spacing**: Use Tailwind's spacing scale (4, 8, 12, 16, etc.)
2. **Color Palette**: Stick to Tailwind's color system for consistency
3. **Component Extraction**: Extract repeated patterns into reusable components
4. **Utility Composition**: Prefer utility classes over `@apply` for maintainability
5. **Semantic HTML**: Use proper HTML elements with Tailwind classes
6. **Performance**: Ensure content paths include all template files for optimal purging
7. **Accessibility**: Include focus styles, ARIA labels, and respect user preferences (reduced-motion)
## Troubleshooting
### Classes Not Applying
- **Check content paths**: Ensure all template files are included in `content: []` in config
- **Verify build**: Run `npm run build` to regenerate purged CSS
- **Dev mode**: Use `npx tailwindcss -o` with `--watch` flag for live updates
### Responsive Styles Not Working
- **Order matters**: Responsive prefixes must come before non-responsive (e.g., `md:flex` not `flex md:flex`)
- **Check breakpoint values**: Verify breakpoints match your design requirements
- **DevTools**: Use browser DevTools responsive mode to test at each breakpoint
### Dark Mode Issues
- **Verify config**: Ensure `darkMode: 'class'` or `'media'` is set correctly
- **Toggle implementation**: Use `document.documentElement.classList.toggle('dark')` for class strategy
- **Initial flash**: Add `dark` class to `<html>` before body renders
## Constraints and Warnings
- **Class Proliferation**: Long class strings reduce readability; extract into components
- **Content Paths**: Misconfigured paths cause classes to be purged in production
- **Arbitrary Values**: Use sparingly; prefer design tokens for consistency
- **Specificity Issues**: Avoid `@apply` with complex selectors
- **Dark Mode**: Requires correct configuration (`class` or `media` strategy)
- **Browser Support**: Check Tailwind docs for compatibility notes
## References
- **[references/layout-patterns.md](references/layout-patterns.md)** — Flexbox, grid, spacing, typography, colors
- **[references/component-patterns.md](references/component-patterns.md)** — Cards, navigation, forms, modals, React patterns
- **[references/responsive-design.md](references/responsive-design.md)** — Responsive patterns, dark mode, container queries
- **[references/animations.md](references/animations.md)** — Transitions, transforms, built-in animations, motion preferences
- **[references/performance.md](references/performance.md)** — Bundle optimization, CSS optimization, production builds
- **[references/accessibility.md](references/accessibility.md)** — Focus management, screen readers, color contrast, ARIA
- **[references/configuration.md](references/configuration.md)** — CSS-first config, JavaScript config, plugins, presets
- **[references/reference.md](references/reference.md)** — Additional reference materials
## External Resources
- [Tailwind CSS Docs](https://tailwindcss.com/docs)
- [Tailwind UI](https://tailwindui.com)
- [Tailwind Play](https://play.tailwindcss.com)
@@ -0,0 +1,167 @@
# Tailwind CSS Accessibility Guidelines
## Focus Management
```html
<!-- Custom focus styles that meet WCAG AA -->
<button class="focus:outline-none focus:ring-4 focus:ring-blue-500 focus:ring-offset-2">
Accessible Button
</button>
<!-- Skip links for keyboard navigation -->
<a href="#main-content" class="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4">
Skip to main content
</a>
```
### Focus Visible vs Focus
```html
<!-- Only show focus ring on keyboard navigation -->
<button class="focus-visible:ring-2 focus-visible:ring-blue-500">
Keyboard-only focus indicator
</button>
```
---
## Screen Reader Support
```html
<!-- Semantic buttons with ARIA labels -->
<button aria-label="Close dialog" class="p-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
<!-- Descriptive links -->
<a href="/docs" aria-describedby="docs-description">
Documentation
</a>
<p id="docs-description" class="sr-only">
Learn how to use our API and integration guides
</p>
<!-- Live regions for dynamic content -->
<div aria-live="polite" class="sr-only">
<p>3 new notifications</p>
</div>
```
### Screen Reader Only Content
```html
<span class="sr-only">Opens in new window</span>
```
---
## Color Contrast
```html
<!-- Ensure sufficient contrast ratios -->
<div class="bg-gray-900 text-white">
High contrast text (WCAG AAA)
</div>
<div class="bg-blue-500 text-blue-100">
Good contrast on colored backgrounds
</div>
<!-- Use contrast utilities for testing -->
<div class="bg-red-500 text-white contrast-more:bg-red-600 contrast-more:text-red-100">
Adjusts for high contrast mode
</div>
```
### Contrast Guidelines
- **Normal text**: Minimum 4.5:1 contrast ratio
- **Large text**: Minimum 3:1 contrast ratio
- **UI components**: Minimum 3:1 contrast ratio
---
## Motion Preferences
```html
<!-- Respect prefers-reduced-motion -->
<div class="transform transition-transform motion-reduce:transition-none">
Doesn't animate when user prefers reduced motion
</div>
<!-- Conditional animations -->
<div class="animate-pulse motion-safe:hover:animate-spin">
Only animates when motion is preferred
</div>
```
### Reduced Motion Media Query
```css
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
```
---
## ARIA Patterns with Tailwind
### Toggle Button
```html
<button
type="button"
role="switch"
aria-checked="false"
class="relative inline-flex h-6 w-11 items-center rounded-full bg-gray-200 aria-checked:bg-blue-600"
>
<span class="sr-only">Enable notifications</span>
<span class="inline-block h-4 w-4 transform rounded-full bg-white transition translate-x-1 aria-checked:translate-x-6"></span>
</button>
```
### Alert Dialog
```html
<div
role="alertdialog"
aria-labelledby="alert-title"
aria-describedby="alert-description"
class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50"
>
<div class="max-w-md w-full bg-white rounded-lg shadow-xl p-6">
<h2 id="alert-title" class="text-lg font-bold mb-2">
Are you sure?
</h2>
<p id="alert-description" class="text-gray-600 mb-4">
This action cannot be undone.
</p>
<div class="flex justify-end gap-2">
<button class="px-4 py-2 text-gray-600">Cancel</button>
<button class="px-4 py-2 bg-red-500 text-white rounded">Delete</button>
</div>
</div>
</div>
```
---
## Accessibility Checklist
- [ ] All interactive elements have visible focus indicators
- [ ] Color contrast meets WCAG standards
- [ ] Images have alt text
- [ ] Form inputs have associated labels
- [ ] ARIA labels used for icon-only buttons
- [ ] Animations respect reduced motion preference
- [ ] Skip links provided for keyboard navigation
- [ ] Landmarks (header, main, nav, footer) used appropriately
@@ -0,0 +1,141 @@
# Tailwind CSS Animations & Transitions
## Basic Transitions
```html
<button class="bg-blue-500 hover:bg-blue-700 transition duration-300">
Smooth transition
</button>
```
### Transition Properties
| Class | Description |
|-------|-------------|
| `transition` | All properties |
| `transition-colors` | Color properties only |
| `transition-opacity` | Opacity only |
| `transition-transform` | Transform only |
| `duration-150` | 150ms duration |
| `duration-300` | 300ms duration |
| `ease-in` | Ease in timing |
| `ease-out` | Ease out timing |
| `ease-in-out` | Ease in-out timing |
---
## Transform Effects
```html
<!-- Scale on hover -->
<div class="transform hover:scale-110 transition duration-300">
Scale on hover
</div>
<!-- Rotate on hover -->
<img class="transform hover:rotate-6 transition duration-300" />
<!-- Multiple transforms -->
<div class="transform hover:scale-105 hover:-translate-y-1 transition">
Scale up and move up
</div>
```
---
## Built-in Animations
```html
<div class="animate-spin">Spinning</div>
<div class="animate-pulse">Pulsing</div>
<div class="animate-bounce">Bouncing</div>
<div class="animate-ping">Pinging (radar effect)</div>
```
### Common Use Cases
```html
<!-- Loading spinner -->
<svg class="animate-spin h-5 w-5 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<!-- Skeleton loading -->
<div class="animate-pulse space-y-4">
<div class="h-4 bg-gray-200 rounded w-3/4"></div>
<div class="h-4 bg-gray-200 rounded"></div>
<div class="h-4 bg-gray-200 rounded w-5/6"></div>
</div>
<!-- Notification badge -->
<span class="absolute -top-1 -right-1 h-3 w-3 animate-ping rounded-full bg-red-400 opacity-75"></span>
<span class="absolute -top-1 -right-1 h-3 w-3 rounded-full bg-red-500"></span>
```
---
## Custom Animations (v4.1+)
```css
/* In your CSS with @theme */
@theme {
--animate-fade-in: fadeIn 0.5s ease-in-out;
--animate-slide-up: slideUp 0.3s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
```
Usage:
```html
<div class="animate-fade-in">Fades in on load</div>
<div class="animate-slide-up">Slides up on load</div>
```
---
## Motion Preferences
Respect user's motion preferences:
```html
<!-- Disable animations for users who prefer reduced motion -->
<div class="transform transition-transform motion-reduce:transition-none">
Doesn't animate when user prefers reduced motion
</div>
<!-- Only animate when motion is preferred -->
<div class="animate-pulse motion-safe:hover:animate-spin">
Only animates when motion is preferred
</div>
```
### Global Reduced Motion Support
```css
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
```
@@ -0,0 +1,169 @@
# Tailwind CSS Component Patterns
## Card Component
```html
<div class="bg-white rounded-lg shadow-lg overflow-hidden">
<img class="w-full h-48 object-cover" src="image.jpg" alt="Card image" />
<div class="p-6">
<h3 class="text-xl font-bold mb-2">Card Title</h3>
<p class="text-gray-700 mb-4">Card description text goes here.</p>
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Action
</button>
</div>
</div>
```
## Responsive User Card
```html
<div class="max-w-sm mx-auto bg-white rounded-xl shadow-lg overflow-hidden sm:flex sm:max-w-2xl">
<img class="h-48 w-full object-cover sm:h-auto sm:w-48"
src="profile.jpg"
alt="Profile" />
<div class="p-8">
<div class="uppercase tracking-wide text-sm text-indigo-500 font-semibold">
Product Engineer
</div>
<h2 class="mt-1 text-xl font-semibold text-gray-900">
John Doe
</h2>
<p class="mt-2 text-gray-500">
Building amazing products with modern technology.
</p>
<button class="mt-4 px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition">
Contact
</button>
</div>
</div>
```
## Navigation Bar
```html
<nav class="bg-white shadow-lg">
<div class="container mx-auto px-4">
<div class="flex justify-between items-center h-16">
<div class="flex items-center">
<a href="#" class="text-xl font-bold text-gray-800">Logo</a>
</div>
<div class="hidden md:flex space-x-8">
<a href="#" class="text-gray-700 hover:text-blue-600 transition">Home</a>
<a href="#" class="text-gray-700 hover:text-blue-600 transition">About</a>
<a href="#" class="text-gray-700 hover:text-blue-600 transition">Services</a>
<a href="#" class="text-gray-700 hover:text-blue-600 transition">Contact</a>
</div>
<button class="md:hidden">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
</button>
</div>
</div>
</nav>
```
## Form Elements
```html
<form class="space-y-6 max-w-md mx-auto">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
Email
</label>
<input
type="email"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="you@example.com"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
Password
</label>
<input
type="password"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
<div class="flex items-center">
<input type="checkbox" class="mr-2" />
<label class="text-sm text-gray-600">Remember me</label>
</div>
<button
type="submit"
class="w-full bg-blue-600 text-white font-semibold py-2 px-4 rounded-lg hover:bg-blue-700 transition"
>
Sign In
</button>
</form>
```
## Modal/Dialog
```html
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4">
<div class="bg-white rounded-lg shadow-xl max-w-md w-full p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold">Modal Title</h3>
<button class="text-gray-500 hover:text-gray-700">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<p class="text-gray-700 mb-6">
Modal content goes here.
</p>
<div class="flex justify-end space-x-4">
<button class="px-4 py-2 text-gray-600 hover:text-gray-800">
Cancel
</button>
<button class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">
Confirm
</button>
</div>
</div>
</div>
```
## React Button Component with Variants
```tsx
import { useState } from 'react';
function Button({
variant = 'primary',
size = 'md',
children
}: {
variant?: 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
children: React.ReactNode;
}) {
const baseClasses = 'font-semibold rounded transition';
const variantClasses = {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
};
const sizeClasses = {
sm: 'px-3 py-1 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg',
};
return (
<button
className={`${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]}`}
>
{children}
</button>
);
}
```
@@ -0,0 +1,198 @@
# Tailwind CSS Configuration
## CSS-First Configuration (v4.1+)
Use the `@theme` directive for CSS-based configuration:
```css
/* src/styles.css */
@import "tailwindcss";
@theme {
/* Custom colors */
--color-brand-50: #f0f9ff;
--color-brand-500: #3b82f6;
--color-brand-900: #1e3a8a;
/* Custom fonts */
--font-display: "Inter", system-ui, sans-serif;
--font-mono: "Fira Code", monospace;
/* Custom spacing */
--spacing-128: 32rem;
/* Custom animations */
--animate-fade-in: fadeIn 0.5s ease-in-out;
/* Custom breakpoints */
--breakpoint-3xl: 1920px;
}
/* Define custom animations */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* Custom utilities */
@utility content-auto {
content-visibility: auto;
}
```
---
## JavaScript Configuration (Legacy)
```javascript
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx,vue,svelte}",
],
theme: {
extend: {
colors: {
primary: {
50: '#f0f9ff',
500: '#3b82f6',
900: '#1e3a8a',
},
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
spacing: {
'128': '32rem',
},
},
},
plugins: [],
}
```
---
## Vite Integration (v4.1+)
```javascript
// vite.config.ts
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [
tailwindcss(),
],
})
```
---
## Advanced v4.1 Features
### Native CSS Custom Properties
```html
<div class="bg-[var(--color-brand-500)] text-[var(--color-white)]">
Using CSS custom properties directly
</div>
```
### Enhanced Arbitrary Values
```html
<!-- Complex grid with custom tracks -->
<div class="grid grid-cols-[repeat(auto-fit,minmax(250px,1fr))] gap-4">
Responsive grid without custom CSS
</div>
<!-- Custom animation timing -->
<div class="animate-bounce ease-[cubic-bezier(0.68,-0.55,0.265,1.55)]">
Bounce with custom easing
</div>
```
### Custom Utilities
```css
@utility content-auto {
content-visibility: auto;
}
@utility text-shadow {
text-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
```
---
## Plugins
### Custom Plugin Example
```javascript
// tailwind.config.js
const plugin = require('tailwindcss/plugin')
module.exports = {
plugins: [
plugin(function({ addUtilities, addComponents, e, config }) {
// Add custom utilities
addUtilities({
'.content-auto': {
contentVisibility: 'auto',
},
})
// Add custom components
addComponents({
'.btn': {
padding: '.5rem 1rem',
borderRadius: '.25rem',
fontWeight: '600',
},
})
}),
],
}
```
---
## Presets
### Creating a Reusable Preset
```javascript
// tailwind-preset.js
module.exports = {
theme: {
extend: {
colors: {
brand: {
DEFAULT: '#3b82f6',
light: '#60a5fa',
dark: '#1d4ed8',
},
},
},
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
],
}
```
### Using a Preset
```javascript
// tailwind.config.js
module.exports = {
presets: [
require('./tailwind-preset.js'),
],
}
```
@@ -0,0 +1,222 @@
# Tailwind CSS Layout Patterns
## Layout Utilities
### Flexbox Layouts
Basic flex container:
```html
<div class="flex items-center justify-between">
<div>Left</div>
<div>Center</div>
<div>Right</div>
</div>
```
Responsive flex direction:
```html
<div class="flex flex-col md:flex-row gap-4">
<div class="flex-1">Item 1</div>
<div class="flex-1">Item 2</div>
<div class="flex-1">Item 3</div>
</div>
```
Common flex patterns:
```html
<!-- Center content -->
<div class="flex items-center justify-center min-h-screen">
<div>Centered Content</div>
</div>
<!-- Space between items -->
<div class="flex justify-between items-center">
<span>Left</span>
<span>Right</span>
</div>
<!-- Vertical stack with gap -->
<div class="flex flex-col gap-4">
<div>Item 1</div>
<div>Item 2</div>
</div>
```
### Grid Layouts
Basic grid:
```html
<div class="grid grid-cols-3 gap-4">
<div>Column 1</div>
<div>Column 2</div>
<div>Column 3</div>
</div>
```
Responsive grid:
```html
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
<!-- 1 column mobile, 2 tablet, 4 desktop -->
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
<div>Item 4</div>
</div>
```
Auto-fit columns:
```html
<div class="grid grid-cols-[repeat(auto-fit,minmax(250px,1fr))] gap-4">
<!-- Automatically fit columns based on container width -->
</div>
```
### Container & Max Width
Centered container with max width:
```html
<div class="container mx-auto px-4 max-w-7xl">
<!-- Centered content with padding -->
</div>
```
Responsive max width:
```html
<div class="w-full max-w-md mx-auto">
<!-- Max 448px width, centered -->
</div>
```
---
## Spacing
### Padding & Margin
Uniform spacing:
```html
<div class="p-4">Padding all sides</div>
<div class="m-4">Margin all sides</div>
```
Individual sides:
```html
<div class="pt-4 pr-8 pb-4 pl-8">
<!-- Top 1rem, Right 2rem, Bottom 1rem, Left 2rem -->
</div>
```
Axis-based spacing:
```html
<div class="px-4 py-8">
<!-- Horizontal padding 1rem, Vertical padding 2rem -->
</div>
```
Responsive spacing:
```html
<div class="p-4 md:p-8 lg:p-12">
<!-- Increases padding at larger breakpoints -->
</div>
```
Space between children:
```html
<div class="space-y-4">
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
</div>
```
---
## Typography
### Font Size & Weight
```html
<h1 class="text-4xl font-bold">Large Heading</h1>
<h2 class="text-2xl font-semibold">Subheading</h2>
<p class="text-base font-normal">Body text</p>
<small class="text-sm text-gray-600">Small text</small>
```
Responsive typography:
```html
<h1 class="text-2xl md:text-4xl lg:text-6xl font-bold">
Responsive Heading
</h1>
```
### Line Height & Letter Spacing
```html
<p class="leading-relaxed tracking-wide">
Text with relaxed line height and wide letter spacing
</p>
```
### Text Alignment
```html
<p class="text-left md:text-center">
Left aligned on mobile, centered on tablet+
</p>
```
---
## Colors
### Background Colors
```html
<div class="bg-blue-500">Blue background</div>
<div class="bg-gray-100">Light gray background</div>
<div class="bg-gradient-to-r from-blue-500 to-purple-600">
Gradient background
</div>
```
### Text Colors
```html
<p class="text-gray-900">Dark text</p>
<p class="text-blue-600">Blue text</p>
<p class="text-red-500">Error text</p>
```
### Opacity
```html
<div class="bg-blue-500 bg-opacity-50">
Semi-transparent blue
</div>
```
---
## Responsive Breakpoints Reference
| Prefix | Min Width | CSS Equivalent |
|--------|-----------|----------------|
| `sm:` | 640px | `@media (min-width: 640px)` |
| `md:` | 768px | `@media (min-width: 768px)` |
| `lg:` | 1024px | `@media (min-width: 1024px)` |
| `xl:` | 1280px | `@media (min-width: 1280px)` |
| `2xl:` | 1536px | `@media (min-width: 1536px)` |
@@ -0,0 +1,119 @@
# Tailwind CSS Performance Optimization
## Bundle Size Optimization
Configure content sources for optimal purging:
```javascript
// tailwind.config.js
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx,vue,svelte}",
"./node_modules/@mycompany/ui-lib/**/*.{js,ts,jsx,tsx}",
],
// Enable JIT for faster builds
jit: true,
}
```
### Content Path Best Practices
1. **Be specific**: Don't use `"./**/*"` - it scans too many files
2. **Include UI libraries**: Add paths to component libraries
3. **Exclude tests**: Don't include test files in content paths
---
## CSS Optimization Techniques
```html
<!-- Use content-visibility for offscreen content -->
<div class="content-visibility-auto">
<div>Heavy content that's initially offscreen</div>
</div>
<!-- Optimize images with aspect-ratio -->
<img class="aspect-video w-full object-cover" src="video.jpg" alt="Video thumbnail" />
<!-- Use contain for paint optimization -->
<div class="contain-layout">
Complex layout that doesn't affect outside elements
</div>
```
---
## Development Performance (v4.1+)
```css
/* Enable CSS-first configuration in v4.1 */
@import "tailwindcss";
@theme {
/* Define once, use everywhere */
--color-brand: #3b82f6;
--font-mono: "Fira Code", monospace;
}
/* Critical CSS for above-the-fold content */
@layer critical {
.hero-title {
@apply text-4xl md:text-6xl font-bold;
}
}
```
---
## Production Build Optimization
### PurgeCSS Configuration
```javascript
// tailwind.config.js
module.exports = {
purge: {
enabled: process.env.NODE_ENV === 'production',
content: [
'./src/**/*.html',
'./src/**/*.jsx',
'./src/**/*.tsx',
],
options: {
safelist: [
'bg-red-500',
'text-center',
// Classes that shouldn't be purged
],
},
},
}
```
### Minification
```bash
# Use cssnano for minification
npm install -D cssnano
# In postcss.config.js
module.exports = {
plugins: [
require('tailwindcss'),
require('autoprefixer'),
...(process.env.NODE_ENV === 'production' ? [require('cssnano')] : []),
],
}
```
---
## Best Practices for Performance
1. **Use the JIT engine**: Faster builds, smaller output
2. **Configure content correctly**: Only include files that use Tailwind
3. **Minimize custom CSS**: Use Tailwind utilities over custom CSS
4. **Enable purging in production**: Removes unused styles
5. **Use `@layer` for custom styles**: Helps with organization and purging
6. **Avoid `@apply` in components**: Prefer composing utilities in markup
@@ -0,0 +1,508 @@
# Tailwind CSS Documentation
Tailwind CSS is a utility-first CSS framework that generates styles by scanning HTML, JavaScript, and template files for class names. It provides a comprehensive design system through CSS utility classes, enabling rapid UI development without writing custom CSS. The framework operates at build-time, analyzing source files and generating only the CSS classes actually used in the project, resulting in optimized production bundles with zero runtime overhead.
The framework includes an extensive default color palette (18 colors with 11 shades each), responsive breakpoint system, customizable design tokens via CSS custom properties, and support for dark mode, pseudo-classes, pseudo-elements, and media queries through variant prefixes. Tailwind CSS v4.1 introduces CSS-first configuration using the `@theme` directive, native support for custom utilities via `@utility`, seamless integration with modern build tools through Vite, PostCSS, and framework-specific plugins, and enhanced arbitrary value syntax for maximum flexibility.
## Installation with Vite
Installing Tailwind CSS using the Vite plugin for modern JavaScript frameworks.
```bash
# Create a new Vite project
npm create vite@latest my-project
cd my-project
# Install Tailwind CSS and Vite plugin
npm install tailwindcss @tailwindcss/vite
```
```javascript
// vite.config.ts
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [
tailwindcss(),
],
})
```
```css
/* src/style.css */
@import "tailwindcss";
```
```html
# Hello world!
```
## Utility Classes with Variants
Applying conditional styles using variant prefixes for hover, focus, and responsive breakpoints.
```html
Save changes
Content adapts to color scheme preference
Submit
```
## Custom Theme Configuration
Defining custom design tokens using the `@theme` directive in CSS.
```css
/* app.css */
@import "tailwindcss";
@theme {
/* Custom fonts */
--font-display: "Satoshi", "sans-serif";
--font-body: "Inter", system-ui, sans-serif;
/* Custom colors */
--color-brand-50: oklch(0.98 0.02 264);
--color-brand-100: oklch(0.95 0.05 264);
--color-brand-500: oklch(0.55 0.22 264);
--color-brand-900: oklch(0.25 0.12 264);
/* Custom breakpoints */
--breakpoint-3xl: 120rem;
--breakpoint-4xl: 160rem;
/* Custom spacing */
--spacing-18: calc(var(--spacing) * 18);
/* Custom animations */
--ease-fluid: cubic-bezier(0.3, 0, 0, 1);
--ease-snappy: cubic-bezier(0.2, 0, 0, 1);
}
```
```html
Custom design system
```
## Arbitrary Values
Using square bracket notation for one-off custom values without leaving HTML.
```html
Pixel-perfect positioning
Custom hex colors, font sizes, and content
Any CSS property
Reference custom properties
Complex grid layouts
Font size from CSS variable
Color from CSS variable
```
## Color System
Working with Tailwind's comprehensive color palette and opacity modifiers.
```html
Color utilities across all properties
Alpha channel with percentage
Arbitrary opacity values
Opacity from CSS variable
Adapts to color scheme
```
## Dark Mode
Implementing dark mode with CSS media queries or manual toggle.
```html
Content automatically adapts
```
```css
/* Manual dark mode toggle with class selector */
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
```
```html
Controlled by .dark class
```
```javascript
// Dark mode toggle logic
// On page load or theme change
document.documentElement.classList.toggle(
"dark",
localStorage.theme === "dark" ||
(!("theme" in localStorage) && window.matchMedia("(prefers-color-scheme: dark)").matches)
);
// User chooses light mode
localStorage.theme = "light";
// User chooses dark mode
localStorage.theme = "dark";
// User chooses system preference
localStorage.removeItem("theme");
```
## State Variants
Styling elements based on pseudo-classes and parent/sibling state.
```html
- Item content
**Title**
Description
Please provide a valid email address.
Option
```
## Responsive Design
Building mobile-first responsive layouts with breakpoint variants.
```html
# Responsive heading
Text scales with viewport
Desktop only
Mobile only
Custom breakpoint
Below medium
```
## Custom Utilities
Creating reusable custom utility classes with variant support.
```css
/* Simple custom utility */
@utility content-auto {
content-visibility: auto;
}
/* Complex utility with nesting */
@utility scrollbar-hidden {
&::-webkit-scrollbar {
display: none;
}
}
/* Functional utility with theme values */
@theme {
--tab-size-2: 2;
--tab-size-4: 4;
--tab-size-github: 8;
}
@utility tab-* {
tab-size: --value(--tab-size-*);
}
/* Supporting arbitrary, bare, and theme values */
@utility opacity-* {
opacity: --value([percentage]);
opacity: calc(--value(integer) * 1%);
opacity: --value(--opacity-*);
}
/* Utility with modifiers */
@utility text-* {
font-size: --value(--text-*, [length]);
line-height: --modifier(--leading-*, [length], [*]);
}
/* Negative value support */
@utility inset-* {
inset: --spacing(--value(integer));
inset: --value([percentage], [length]);
}
@utility -inset-* {
inset: --spacing(--value(integer) * -1);
inset: calc(--value([percentage], [length]) * -1);
}
```
```html
Custom utilities work with variants
Variants and arbitrary values supported
Utility with modifier (font-size/line-height)
```
## Custom Variants
Registering custom conditional styles with the `@custom-variant` directive.
```css
/* Simple custom variant */
@custom-variant theme-midnight (&:where([data-theme="midnight"] *));
/* Variant with media query */
@custom-variant any-hover {
@media (any-hover: hover) {
&:hover {
@slot;
}
}
}
/* ARIA state variant */
@custom-variant aria-asc (&[aria-sort="ascending"]);
@custom-variant aria-desc (&[aria-sort="descending"]);
/* Data attribute variant */
@custom-variant data-checked (&[data-ui~="checked"]);
```
```html
Midnight theme button
Sortable column
Checked state
One-off custom selectors
```
## Applying Variants in CSS
Using the `@variant` directive to apply variants within custom CSS.
```css
/* Single variant */
.my-element {
background: white;
@variant dark {
background: black;
}
}
/* Nested variants */
.my-button {
background: white;
@variant dark {
background: gray;
@variant hover {
background: black;
}
}
}
/* Compiled output */
.my-element {
background: white;
}
@media (prefers-color-scheme: dark) {
.my-element {
background: black;
}
}
```
## Layer Organization
Organizing custom styles into Tailwind's cascade layers.
```css
@import "tailwindcss";
/* Base styles for HTML elements */
@layer base {
h1 {
font-size: var(--text-2xl);
font-weight: bold;
}
h2 {
font-size: var(--text-xl);
font-weight: 600;
}
body {
font-family: var(--font-body);
}
}
/* Reusable component classes */
@layer components {
.btn {
padding: --spacing(2) --spacing(4);
border-radius: var(--radius);
font-weight: 600;
transition: all 150ms;
}
.btn-primary {
background-color: var(--color-blue-500);
color: white;
}
.card {
background-color: var(--color-white);
border-radius: var(--radius-lg);
padding: --spacing(6);
box-shadow: var(--shadow-xl);
}
/* Third-party component overrides */
.select2-dropdown {
border-radius: var(--radius);
box-shadow: var(--shadow-lg);
}
}
```
```html
Square corners despite card class
Component with utility overrides
```
## Functions and Directives
Using Tailwind's CSS functions for dynamic values and opacity adjustments.
```css
/* Alpha function for opacity */
.my-element {
color: --alpha(var(--color-lime-300) / 50%);
background: --alpha(var(--color-blue-500) / 25%);
}
/* Spacing function */
.my-element {
margin: --spacing(4);
padding: calc(--spacing(6) - 1px);
}
/* In arbitrary values */
/* Source directive for additional content */
@source "../node_modules/@my-company/ui-lib";
/* Apply directive for inline utilities */
.select2-dropdown {
@apply rounded-b-lg shadow-md;
}
.select2-search {
@apply rounded border border-gray-300;
}
.select2-results__group {
@apply text-lg font-bold text-gray-900;
}
```
## Pseudo-elements
Styling ::before, ::after, ::placeholder, and other pseudo-elements.
```html
Email
- First item
- Second item
Select this text to see custom colors
Typography with pseudo-elements
```
## Media Queries
Conditional styling based on user preferences and device capabilities.
```html
Respects user preference
Only animates if motion allowed
Adjusts for contrast needs
Hidden in portrait mode
Layout adapts to orientation
Not shown when printing
Only visible in print
Progressive enhancement
```
## Summary
Tailwind CSS provides a complete utility-first design system that eliminates the need for writing custom CSS in most cases. The framework's primary use cases include rapid prototyping, building production applications with consistent design systems, creating responsive layouts, implementing dark mode, and maintaining design consistency across large teams. By using utility classes directly in markup, developers can iterate quickly, avoid naming conventions, and prevent CSS bloat since only used styles are generated.
The v4.1 release enhances the developer experience with CSS-first configuration, eliminating JavaScript configuration files for most projects. Integration patterns include using the Vite plugin for modern frameworks, PostCSS for custom build pipelines, the Tailwind CLI for simple projects, and CDN scripts for rapid prototyping. The framework excels at component-driven development when combined with React, Vue, Svelte, or other modern frameworks, where utility classes are co-located with component logic. Custom design systems can be fully defined in CSS using `@theme`, with project-specific utilities and variants extending the framework's capabilities without writing JavaScript plugins.
@@ -0,0 +1,159 @@
# Tailwind CSS Responsive Design & Dark Mode
## Responsive Design Patterns
### Mobile-First Responsive Layout
```html
<div class="container mx-auto px-4">
<!-- Hero Section -->
<div class="flex flex-col md:flex-row items-center gap-8 py-12">
<div class="flex-1">
<h1 class="text-3xl md:text-5xl font-bold mb-4">
Welcome to Our Site
</h1>
<p class="text-lg text-gray-600 mb-6">
Build amazing things with Tailwind CSS
</p>
<button class="bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700">
Get Started
</button>
</div>
<div class="flex-1">
<img src="hero.jpg" class="w-full rounded-lg shadow-lg" />
</div>
</div>
</div>
```
### Responsive Grid Gallery
```html
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 p-4">
<div class="aspect-square bg-gray-200 rounded-lg overflow-hidden">
<img src="image1.jpg" class="w-full h-full object-cover hover:scale-105 transition" />
</div>
<div class="aspect-square bg-gray-200 rounded-lg overflow-hidden">
<img src="image2.jpg" class="w-full h-full object-cover hover:scale-105 transition" />
</div>
<!-- More items... -->
</div>
```
### Responsive Card Component
```tsx
function ProductCard({ product }: { product: Product }) {
return (
<div className="bg-white rounded-lg shadow-lg overflow-hidden
sm:flex sm:max-w-2xl">
<img
className="h-48 w-full object-cover sm:h-auto sm:w-48"
src={product.image}
alt={product.name}
/>
<div className="p-6">
<h3 className="text-lg font-semibold text-gray-900">
{product.name}
</h3>
<p className="mt-2 text-gray-600">
{product.description}
</p>
<button className="mt-4 px-4 py-2 bg-indigo-600 text-white
rounded-lg hover:bg-indigo-700 transition">
Add to Cart
</button>
</div>
</div>
);
}
```
---
## Dark Mode
### Basic Dark Mode Support
```html
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
<h1 class="text-gray-900 dark:text-white">Title</h1>
<p class="text-gray-600 dark:text-gray-400">Description</p>
</div>
```
Enable dark mode in tailwind.config.js:
```javascript
module.exports = {
darkMode: 'class', // or 'media'
// ...
}
```
### Dark Mode Toggle (React)
```tsx
function ThemeToggle() {
const [darkMode, setDarkMode] = useState(false);
useEffect(() => {
if (darkMode) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}, [darkMode]);
return (
<button
onClick={() => setDarkMode(!darkMode)}
className="p-2 rounded-lg bg-gray-200 dark:bg-gray-800"
>
{darkMode ? '🌙' : '☀️'}
</button>
);
}
```
### Dark Mode Best Practices
1. **Use semantic color names**: Instead of `bg-white` use `bg-surface` custom color
2. **Test with real content**: Some colors look good in light but not in dark
3. **Respect system preference**: Use `darkMode: 'media'` for OS-level preference
4. **Smooth transitions**: Add transition utilities for theme changes
```css
/* Global transition for theme changes */
* {
@apply transition-colors duration-200;
}
```
---
## Container Queries (v4.1+)
Component that responds to its container size, not viewport:
```html
<div class="@container">
<div class="@lg:text-xl @2xl:text-2xl">
Text size based on container, not viewport
</div>
</div>
```
Usage in a card component:
```html
<div class="@container w-full">
<div class="flex flex-col @[400px]:flex-row gap-4">
<img class="w-full @[400px]:w-32 h-32 object-cover" src="image.jpg" />
<div>
<h3 class="text-base @[400px]:text-lg font-bold">Title</h3>
<p class="text-sm @[400px]:text-base">Description</p>
</div>
</div>
</div>
```
@@ -0,0 +1,717 @@
---
name: typescript-advanced-types
description: Master TypeScript's advanced type system including generics, conditional types, mapped types, template literals, and utility types for building type-safe applications. Use when implementing complex type logic, creating reusable type utilities, or ensuring compile-time type safety in TypeScript projects.
---
# TypeScript Advanced Types
Comprehensive guidance for mastering TypeScript's advanced type system including generics, conditional types, mapped types, template literal types, and utility types for building robust, type-safe applications.
## When to Use This Skill
- Building type-safe libraries or frameworks
- Creating reusable generic components
- Implementing complex type inference logic
- Designing type-safe API clients
- Building form validation systems
- Creating strongly-typed configuration objects
- Implementing type-safe state management
- Migrating JavaScript codebases to TypeScript
## Core Concepts
### 1. Generics
**Purpose:** Create reusable, type-flexible components while maintaining type safety.
**Basic Generic Function:**
```typescript
function identity<T>(value: T): T {
return value;
}
const num = identity<number>(42); // Type: number
const str = identity<string>("hello"); // Type: string
const auto = identity(true); // Type inferred: boolean
```
**Generic Constraints:**
```typescript
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(item: T): T {
console.log(item.length);
return item;
}
logLength("hello"); // OK: string has length
logLength([1, 2, 3]); // OK: array has length
logLength({ length: 10 }); // OK: object has length
// logLength(42); // Error: number has no length
```
**Multiple Type Parameters:**
```typescript
function merge<T, U>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
const merged = merge({ name: "John" }, { age: 30 });
// Type: { name: string } & { age: number }
```
### 2. Conditional Types
**Purpose:** Create types that depend on conditions, enabling sophisticated type logic.
**Basic Conditional Type:**
```typescript
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
```
**Extracting Return Types:**
```typescript
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function getUser() {
return { id: 1, name: "John" };
}
type User = ReturnType<typeof getUser>;
// Type: { id: number; name: string; }
```
**Distributive Conditional Types:**
```typescript
type ToArray<T> = T extends any ? T[] : never;
type StrOrNumArray = ToArray<string | number>;
// Type: string[] | number[]
```
**Nested Conditions:**
```typescript
type TypeName<T> = T extends string
? "string"
: T extends number
? "number"
: T extends boolean
? "boolean"
: T extends undefined
? "undefined"
: T extends Function
? "function"
: "object";
type T1 = TypeName<string>; // "string"
type T2 = TypeName<() => void>; // "function"
```
### 3. Mapped Types
**Purpose:** Transform existing types by iterating over their properties.
**Basic Mapped Type:**
```typescript
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface User {
id: number;
name: string;
}
type ReadonlyUser = Readonly<User>;
// Type: { readonly id: number; readonly name: string; }
```
**Optional Properties:**
```typescript
type Partial<T> = {
[P in keyof T]?: T[P];
};
type PartialUser = Partial<User>;
// Type: { id?: number; name?: string; }
```
**Key Remapping:**
```typescript
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface Person {
name: string;
age: number;
}
type PersonGetters = Getters<Person>;
// Type: { getName: () => string; getAge: () => number; }
```
**Filtering Properties:**
```typescript
type PickByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K];
};
interface Mixed {
id: number;
name: string;
age: number;
active: boolean;
}
type OnlyNumbers = PickByType<Mixed, number>;
// Type: { id: number; age: number; }
```
### 4. Template Literal Types
**Purpose:** Create string-based types with pattern matching and transformation.
**Basic Template Literal:**
```typescript
type EventName = "click" | "focus" | "blur";
type EventHandler = `on${Capitalize<EventName>}`;
// Type: "onClick" | "onFocus" | "onBlur"
```
**String Manipulation:**
```typescript
type UppercaseGreeting = Uppercase<"hello">; // "HELLO"
type LowercaseGreeting = Lowercase<"HELLO">; // "hello"
type CapitalizedName = Capitalize<"john">; // "John"
type UncapitalizedName = Uncapitalize<"John">; // "john"
```
**Path Building:**
```typescript
type Path<T> = T extends object
? {
[K in keyof T]: K extends string ? `${K}` | `${K}.${Path<T[K]>}` : never;
}[keyof T]
: never;
interface Config {
server: {
host: string;
port: number;
};
database: {
url: string;
};
}
type ConfigPath = Path<Config>;
// Type: "server" | "database" | "server.host" | "server.port" | "database.url"
```
### 5. Utility Types
**Built-in Utility Types:**
```typescript
// Partial<T> - Make all properties optional
type PartialUser = Partial<User>;
// Required<T> - Make all properties required
type RequiredUser = Required<PartialUser>;
// Readonly<T> - Make all properties readonly
type ReadonlyUser = Readonly<User>;
// Pick<T, K> - Select specific properties
type UserName = Pick<User, "name" | "email">;
// Omit<T, K> - Remove specific properties
type UserWithoutPassword = Omit<User, "password">;
// Exclude<T, U> - Exclude types from union
type T1 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
// Extract<T, U> - Extract types from union
type T2 = Extract<"a" | "b" | "c", "a" | "b">; // "a" | "b"
// NonNullable<T> - Exclude null and undefined
type T3 = NonNullable<string | null | undefined>; // string
// Record<K, T> - Create object type with keys K and values T
type PageInfo = Record<"home" | "about", { title: string }>;
```
## Advanced Patterns
### Pattern 1: Type-Safe Event Emitter
```typescript
type EventMap = {
"user:created": { id: string; name: string };
"user:updated": { id: string };
"user:deleted": { id: string };
};
class TypedEventEmitter<T extends Record<string, any>> {
private listeners: {
[K in keyof T]?: Array<(data: T[K]) => void>;
} = {};
on<K extends keyof T>(event: K, callback: (data: T[K]) => void): void {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event]!.push(callback);
}
emit<K extends keyof T>(event: K, data: T[K]): void {
const callbacks = this.listeners[event];
if (callbacks) {
callbacks.forEach((callback) => callback(data));
}
}
}
const emitter = new TypedEventEmitter<EventMap>();
emitter.on("user:created", (data) => {
console.log(data.id, data.name); // Type-safe!
});
emitter.emit("user:created", { id: "1", name: "John" });
// emitter.emit("user:created", { id: "1" }); // Error: missing 'name'
```
### Pattern 2: Type-Safe API Client
```typescript
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type EndpointConfig = {
"/users": {
GET: { response: User[] };
POST: { body: { name: string; email: string }; response: User };
};
"/users/:id": {
GET: { params: { id: string }; response: User };
PUT: { params: { id: string }; body: Partial<User>; response: User };
DELETE: { params: { id: string }; response: void };
};
};
type ExtractParams<T> = T extends { params: infer P } ? P : never;
type ExtractBody<T> = T extends { body: infer B } ? B : never;
type ExtractResponse<T> = T extends { response: infer R } ? R : never;
class APIClient<Config extends Record<string, Record<HTTPMethod, any>>> {
async request<Path extends keyof Config, Method extends keyof Config[Path]>(
path: Path,
method: Method,
...[options]: ExtractParams<Config[Path][Method]> extends never
? ExtractBody<Config[Path][Method]> extends never
? []
: [{ body: ExtractBody<Config[Path][Method]> }]
: [
{
params: ExtractParams<Config[Path][Method]>;
body?: ExtractBody<Config[Path][Method]>;
},
]
): Promise<ExtractResponse<Config[Path][Method]>> {
// Implementation here
return {} as any;
}
}
const api = new APIClient<EndpointConfig>();
// Type-safe API calls
const users = await api.request("/users", "GET");
// Type: User[]
const newUser = await api.request("/users", "POST", {
body: { name: "John", email: "john@example.com" },
});
// Type: User
const user = await api.request("/users/:id", "GET", {
params: { id: "123" },
});
// Type: User
```
### Pattern 3: Builder Pattern with Type Safety
```typescript
type BuilderState<T> = {
[K in keyof T]: T[K] | undefined;
};
type RequiredKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? never : K;
}[keyof T];
type OptionalKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? K : never;
}[keyof T];
type IsComplete<T, S> =
RequiredKeys<T> extends keyof S
? S[RequiredKeys<T>] extends undefined
? false
: true
: false;
class Builder<T, S extends BuilderState<T> = {}> {
private state: S = {} as S;
set<K extends keyof T>(key: K, value: T[K]): Builder<T, S & Record<K, T[K]>> {
this.state[key] = value;
return this as any;
}
build(this: IsComplete<T, S> extends true ? this : never): T {
return this.state as T;
}
}
interface User {
id: string;
name: string;
email: string;
age?: number;
}
const builder = new Builder<User>();
const user = builder
.set("id", "1")
.set("name", "John")
.set("email", "john@example.com")
.build(); // OK: all required fields set
// const incomplete = builder
// .set("id", "1")
// .build(); // Error: missing required fields
```
### Pattern 4: Deep Readonly/Partial
```typescript
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? T[P] extends Function
? T[P]
: DeepReadonly<T[P]>
: T[P];
};
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object
? T[P] extends Array<infer U>
? Array<DeepPartial<U>>
: DeepPartial<T[P]>
: T[P];
};
interface Config {
server: {
host: string;
port: number;
ssl: {
enabled: boolean;
cert: string;
};
};
database: {
url: string;
pool: {
min: number;
max: number;
};
};
}
type ReadonlyConfig = DeepReadonly<Config>;
// All nested properties are readonly
type PartialConfig = DeepPartial<Config>;
// All nested properties are optional
```
### Pattern 5: Type-Safe Form Validation
```typescript
type ValidationRule<T> = {
validate: (value: T) => boolean;
message: string;
};
type FieldValidation<T> = {
[K in keyof T]?: ValidationRule<T[K]>[];
};
type ValidationErrors<T> = {
[K in keyof T]?: string[];
};
class FormValidator<T extends Record<string, any>> {
constructor(private rules: FieldValidation<T>) {}
validate(data: T): ValidationErrors<T> | null {
const errors: ValidationErrors<T> = {};
let hasErrors = false;
for (const key in this.rules) {
const fieldRules = this.rules[key];
const value = data[key];
if (fieldRules) {
const fieldErrors: string[] = [];
for (const rule of fieldRules) {
if (!rule.validate(value)) {
fieldErrors.push(rule.message);
}
}
if (fieldErrors.length > 0) {
errors[key] = fieldErrors;
hasErrors = true;
}
}
}
return hasErrors ? errors : null;
}
}
interface LoginForm {
email: string;
password: string;
}
const validator = new FormValidator<LoginForm>({
email: [
{
validate: (v) => v.includes("@"),
message: "Email must contain @",
},
{
validate: (v) => v.length > 0,
message: "Email is required",
},
],
password: [
{
validate: (v) => v.length >= 8,
message: "Password must be at least 8 characters",
},
],
});
const errors = validator.validate({
email: "invalid",
password: "short",
});
// Type: { email?: string[]; password?: string[]; } | null
```
### Pattern 6: Discriminated Unions
```typescript
type Success<T> = {
status: "success";
data: T;
};
type Error = {
status: "error";
error: string;
};
type Loading = {
status: "loading";
};
type AsyncState<T> = Success<T> | Error | Loading;
function handleState<T>(state: AsyncState<T>): void {
switch (state.status) {
case "success":
console.log(state.data); // Type: T
break;
case "error":
console.log(state.error); // Type: string
break;
case "loading":
console.log("Loading...");
break;
}
}
// Type-safe state machine
type State =
| { type: "idle" }
| { type: "fetching"; requestId: string }
| { type: "success"; data: any }
| { type: "error"; error: Error };
type Event =
| { type: "FETCH"; requestId: string }
| { type: "SUCCESS"; data: any }
| { type: "ERROR"; error: Error }
| { type: "RESET" };
function reducer(state: State, event: Event): State {
switch (state.type) {
case "idle":
return event.type === "FETCH"
? { type: "fetching", requestId: event.requestId }
: state;
case "fetching":
if (event.type === "SUCCESS") {
return { type: "success", data: event.data };
}
if (event.type === "ERROR") {
return { type: "error", error: event.error };
}
return state;
case "success":
case "error":
return event.type === "RESET" ? { type: "idle" } : state;
}
}
```
## Type Inference Techniques
### 1. Infer Keyword
```typescript
// Extract array element type
type ElementType<T> = T extends (infer U)[] ? U : never;
type NumArray = number[];
type Num = ElementType<NumArray>; // number
// Extract promise type
type PromiseType<T> = T extends Promise<infer U> ? U : never;
type AsyncNum = PromiseType<Promise<number>>; // number
// Extract function parameters
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
function foo(a: string, b: number) {}
type FooParams = Parameters<typeof foo>; // [string, number]
```
### 2. Type Guards
```typescript
function isString(value: unknown): value is string {
return typeof value === "string";
}
function isArrayOf<T>(
value: unknown,
guard: (item: unknown) => item is T,
): value is T[] {
return Array.isArray(value) && value.every(guard);
}
const data: unknown = ["a", "b", "c"];
if (isArrayOf(data, isString)) {
data.forEach((s) => s.toUpperCase()); // Type: string[]
}
```
### 3. Assertion Functions
```typescript
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== "string") {
throw new Error("Not a string");
}
}
function processValue(value: unknown) {
assertIsString(value);
// value is now typed as string
console.log(value.toUpperCase());
}
```
## Best Practices
1. **Use `unknown` over `any`**: Enforce type checking
2. **Prefer `interface` for object shapes**: Better error messages
3. **Use `type` for unions and complex types**: More flexible
4. **Leverage type inference**: Let TypeScript infer when possible
5. **Create helper types**: Build reusable type utilities
6. **Use const assertions**: Preserve literal types
7. **Avoid type assertions**: Use type guards instead
8. **Document complex types**: Add JSDoc comments
9. **Use strict mode**: Enable all strict compiler options
10. **Test your types**: Use type tests to verify type behavior
## Type Testing
```typescript
// Type assertion tests
type AssertEqual<T, U> = [T] extends [U]
? [U] extends [T]
? true
: false
: false;
type Test1 = AssertEqual<string, string>; // true
type Test2 = AssertEqual<string, number>; // false
type Test3 = AssertEqual<string | number, string>; // false
// Expect error helper
type ExpectError<T extends never> = T;
// Example usage
type ShouldError = ExpectError<AssertEqual<string, number>>;
```
## Common Pitfalls
1. **Over-using `any`**: Defeats the purpose of TypeScript
2. **Ignoring strict null checks**: Can lead to runtime errors
3. **Too complex types**: Can slow down compilation
4. **Not using discriminated unions**: Misses type narrowing opportunities
5. **Forgetting readonly modifiers**: Allows unintended mutations
6. **Circular type references**: Can cause compiler errors
7. **Not handling edge cases**: Like empty arrays or null values
## Performance Considerations
- Avoid deeply nested conditional types
- Use simple types when possible
- Cache complex type computations
- Limit recursion depth in recursive types
- Use build tools to skip type checking in production
@@ -0,0 +1,5 @@
# Generation Info
- **Source:** `sources/vite`
- **Git SHA:** `c47015eba4f0de255218c35769628d87152216ca`
- **Generated:** 2026-01-31
+72
View File
@@ -0,0 +1,72 @@
---
name: vite
description: Vite build tool configuration, plugin API, SSR, and Vite 8 Rolldown migration. Use when working with Vite projects, vite.config.ts, Vite plugins, or building libraries/SSR apps with Vite.
metadata:
author: Anthony Fu
version: "2026.1.31"
source: Generated from https://github.com/vitejs/vite, scripts at https://github.com/antfu/skills
---
# Vite
> Based on Vite 8 beta (Rolldown-powered). Vite 8 uses Rolldown bundler and Oxc transformer.
Vite is a next-generation frontend build tool with fast dev server (native ESM + HMR) and optimized production builds.
## Preferences
- Use TypeScript: prefer `vite.config.ts`
- Always use ESM, avoid CommonJS
## Core
| Topic | Description | Reference |
|-------|-------------|-----------|
| Configuration | `vite.config.ts`, `defineConfig`, conditional configs, `loadEnv` | [core-config](references/core-config.md) |
| Features | `import.meta.glob`, asset queries (`?raw`, `?url`), `import.meta.env`, HMR API | [core-features](references/core-features.md) |
| Plugin API | Vite-specific hooks, virtual modules, plugin ordering | [core-plugin-api](references/core-plugin-api.md) |
## Build & SSR
| Topic | Description | Reference |
|-------|-------------|-----------|
| Build & SSR | Library mode, SSR middleware mode, `ssrLoadModule`, JavaScript API | [build-and-ssr](references/build-and-ssr.md) |
## Advanced
| Topic | Description | Reference |
|-------|-------------|-----------|
| Environment API | Vite 6+ multi-environment support, custom runtimes | [environment-api](references/environment-api.md) |
| Rolldown Migration | Vite 8 changes: Rolldown bundler, Oxc transformer, config migration | [rolldown-migration](references/rolldown-migration.md) |
## Quick Reference
### CLI Commands
```bash
vite # Start dev server
vite build # Production build
vite preview # Preview production build
vite build --ssr # SSR build
```
### Common Config
```ts
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [],
resolve: { alias: { '@': '/src' } },
server: { port: 3000, proxy: { '/api': 'http://localhost:8080' } },
build: { target: 'esnext', outDir: 'dist' },
})
```
### Official Plugins
- `@vitejs/plugin-vue` - Vue 3 SFC support
- `@vitejs/plugin-vue-jsx` - Vue 3 JSX
- `@vitejs/plugin-react` - React with Oxc/Babel
- `@vitejs/plugin-react-swc` - React with SWC
- `@vitejs/plugin-legacy` - Legacy browser support
@@ -0,0 +1,164 @@
---
name: vite-build-ssr
description: Vite library mode, multi-page apps, JavaScript API, and SSR guidance
---
# Build and SSR
## Library Mode
Build a library for distribution:
```ts
// vite.config.ts
import { resolve } from 'node:path'
import { defineConfig } from 'vite'
export default defineConfig({
build: {
lib: {
entry: resolve(import.meta.dirname, 'lib/main.ts'),
name: 'MyLib',
fileName: 'my-lib',
},
rolldownOptions: {
external: ['vue', 'react'],
output: {
globals: {
vue: 'Vue',
react: 'React',
},
},
},
},
})
```
### Multiple Entries
```ts
build: {
lib: {
entry: {
'my-lib': resolve(import.meta.dirname, 'lib/main.ts'),
secondary: resolve(import.meta.dirname, 'lib/secondary.ts'),
},
name: 'MyLib',
},
}
```
### Output Formats
- Single entry: `es` and `umd`
- Multiple entries: `es` and `cjs`
### Package.json Setup
```json
{
"name": "my-lib",
"type": "module",
"files": ["dist"],
"main": "./dist/my-lib.umd.cjs",
"module": "./dist/my-lib.js",
"exports": {
".": {
"import": "./dist/my-lib.js",
"require": "./dist/my-lib.umd.cjs"
},
"./style.css": "./dist/my-lib.css"
}
}
```
## Multi-Page App
```ts
export default defineConfig({
build: {
rolldownOptions: {
input: {
main: resolve(import.meta.dirname, 'index.html'),
nested: resolve(import.meta.dirname, 'nested/index.html'),
},
},
},
})
```
## SSR Development
**Note:** Vite's SSR support is **low-level** and designed mostly for meta-framework authors, not application developers. If you need SSR for your app, use a Vite-based meta-framework instead:
- **Nuxt** (Vue) - https://nuxt.com
- **SvelteKit** (Svelte) - https://svelte.dev/docs/kit
- **SolidStart** (Solid) - https://start.solidjs.com
- **TanStack Start** (React) - https://tanstack.com/start
These frameworks build on top of Vite's SSR primitives so you don't have to wire them up yourself.
**Need a server?** Consider [Nitro](https://nitro.build) -- think of it as "Vite for servers." Nitro provides a portable, framework-agnostic server layer with file-based API routing, auto-imports, and deployment presets for dozens of platforms (Node.js, Deno, Bun, Cloudflare Workers, Vercel, Netlify, etc.). It integrates naturally with Vite and is what powers Nuxt's server engine. See the [Nitro docs](https://nitro.build) for more details.
## JavaScript API
### createServer
```ts
import { createServer } from 'vite'
const server = await createServer({
configFile: false,
root: import.meta.dirname,
server: { port: 1337 },
})
await server.listen()
server.printUrls()
```
### build
```ts
import { build } from 'vite'
await build({
root: './project',
build: { outDir: 'dist' },
})
```
### preview
```ts
import { preview } from 'vite'
const previewServer = await preview({
preview: { port: 8080, open: true },
})
previewServer.printUrls()
```
### resolveConfig
```ts
import { resolveConfig } from 'vite'
const config = await resolveConfig({}, 'build')
```
### loadEnv
```ts
import { loadEnv } from 'vite'
const env = loadEnv('development', process.cwd(), '')
// Loads all env vars (empty prefix = no filtering)
```
<!--
Source references:
- https://vite.dev/guide/build
- https://vite.dev/guide/api-javascript
- https://nitro.build
-->
@@ -0,0 +1,162 @@
---
name: vite-config
description: Vite configuration patterns using vite.config.ts
---
# Vite Configuration
## Basic Setup
```ts
// vite.config.ts
import { defineConfig } from 'vite'
export default defineConfig({
// config options
})
```
Vite auto-resolves `vite.config.ts` from project root. Supports ES modules syntax regardless of `package.json` type.
## Conditional Config
Export a function to access command and mode:
```ts
export default defineConfig(({ command, mode, isSsrBuild, isPreview }) => {
if (command === 'serve') {
return { /* dev config */ }
} else {
return { /* build config */ }
}
})
```
- `command`: `'serve'` during dev, `'build'` for production
- `mode`: `'development'` or `'production'` (or custom via `--mode`)
## Async Config
```ts
export default defineConfig(async ({ command, mode }) => {
const data = await fetchSomething()
return { /* config */ }
})
```
## Using Environment Variables in Config
`.env` files are loaded **after** config resolution. Use `loadEnv` to access them in config:
```ts
import { defineConfig, loadEnv } from 'vite'
export default defineConfig(({ mode }) => {
// Load env files from cwd, include all vars (empty prefix)
const env = loadEnv(mode, process.cwd(), '')
return {
define: {
__APP_ENV__: JSON.stringify(env.APP_ENV),
},
server: {
port: env.APP_PORT ? Number(env.APP_PORT) : 5173,
},
}
})
```
## Key Config Options
### resolve.alias
```ts
export default defineConfig({
resolve: {
alias: {
'@': '/src',
'~': '/src',
},
},
})
```
### define (Global Constants)
```ts
export default defineConfig({
define: {
__APP_VERSION__: JSON.stringify('1.0.0'),
__API_URL__: 'window.__backend_api_url',
},
})
```
Values must be JSON-serializable or single identifiers. Non-strings auto-wrapped with `JSON.stringify`.
### plugins
```ts
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
})
```
Plugins array is flattened; falsy values ignored.
### server.proxy
```ts
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
})
```
### build.target
Default: Baseline Widely Available browsers. Customize:
```ts
export default defineConfig({
build: {
target: 'esnext', // or 'es2020', ['chrome90', 'firefox88']
},
})
```
## TypeScript Intellisense
For plain JS config files:
```js
/** @type {import('vite').UserConfig} */
export default {
// ...
}
```
Or use `satisfies`:
```ts
import type { UserConfig } from 'vite'
export default {
// ...
} satisfies UserConfig
```
<!--
Source references:
- https://vite.dev/config/
- https://vite.dev/guide/
-->
@@ -0,0 +1,205 @@
---
name: vite-features
description: Vite-specific import patterns and runtime features
---
# Vite Features
## Glob Import
Import multiple modules matching a pattern:
```ts
const modules = import.meta.glob('./dir/*.ts')
// { './dir/foo.ts': () => import('./dir/foo.ts'), ... }
for (const path in modules) {
modules[path]().then((mod) => {
console.log(path, mod)
})
}
```
### Eager Loading
```ts
const modules = import.meta.glob('./dir/*.ts', { eager: true })
// Modules loaded immediately, no dynamic import
```
### Named Imports
```ts
const modules = import.meta.glob('./dir/*.ts', { import: 'setup' })
// Only imports the 'setup' export from each module
const defaults = import.meta.glob('./dir/*.ts', { import: 'default', eager: true })
```
### Multiple Patterns
```ts
const modules = import.meta.glob(['./dir/*.ts', './another/*.ts'])
```
### Negative Patterns
```ts
const modules = import.meta.glob(['./dir/*.ts', '!**/ignored.ts'])
```
### Custom Queries
```ts
const svgRaw = import.meta.glob('./icons/*.svg', { query: '?raw', import: 'default' })
const svgUrls = import.meta.glob('./icons/*.svg', { query: '?url', import: 'default' })
```
## Asset Import Queries
### URL Import
```ts
import imgUrl from './img.png'
// Returns resolved URL: '/src/img.png' (dev) or '/assets/img.2d8efhg.png' (build)
```
### Explicit URL
```ts
import workletUrl from './worklet.js?url'
```
### Raw String
```ts
import shaderCode from './shader.glsl?raw'
```
### Inline/No-Inline
```ts
import inlined from './small.png?inline' // Force base64 inline
import notInlined from './large.png?no-inline' // Force separate file
```
### Web Workers
```ts
import Worker from './worker.ts?worker'
const worker = new Worker()
// Or inline:
import InlineWorker from './worker.ts?worker&inline'
```
Preferred pattern using constructor:
```ts
const worker = new Worker(new URL('./worker.ts', import.meta.url), {
type: 'module',
})
```
## Environment Variables
### Built-in Constants
```ts
import.meta.env.MODE // 'development' | 'production' | custom
import.meta.env.BASE_URL // Base URL from config
import.meta.env.PROD // true in production
import.meta.env.DEV // true in development
import.meta.env.SSR // true when running in server
```
### Custom Variables
Only `VITE_` prefixed vars exposed to client:
```
# .env
VITE_API_URL=https://api.example.com
DB_PASSWORD=secret # NOT exposed to client
```
```ts
console.log(import.meta.env.VITE_API_URL) // works
console.log(import.meta.env.DB_PASSWORD) // undefined
```
### Mode-specific Files
```
.env # always loaded
.env.local # always loaded, gitignored
.env.[mode] # only in specified mode
.env.[mode].local # only in specified mode, gitignored
```
### TypeScript Support
```ts
// vite-env.d.ts
interface ImportMetaEnv {
readonly VITE_API_URL: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
```
### HTML Replacement
```html
<p>Running in %MODE%</p>
<script>window.API = "%VITE_API_URL%"</script>
```
## CSS Modules
Any `.module.css` file treated as CSS module:
```ts
import styles from './component.module.css'
element.className = styles.button
```
With camelCase conversion:
```ts
// .my-class -> myClass (if css.modules.localsConvention configured)
import { myClass } from './component.module.css'
```
## JSON Import
```ts
import pkg from './package.json'
import { version } from './package.json' // Named import with tree-shaking
```
## HMR API
```ts
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
// Handle update
})
import.meta.hot.dispose((data) => {
// Cleanup before module is replaced
})
import.meta.hot.invalidate() // Force full reload
}
```
<!--
Source references:
- https://vite.dev/guide/features
- https://vite.dev/guide/env-and-mode
- https://vite.dev/guide/assets
- https://vite.dev/guide/api-hmr
-->
@@ -0,0 +1,235 @@
---
name: vite-plugin-api
description: Vite plugin authoring with Vite-specific hooks
---
# Vite Plugin API
Vite plugins extend Rolldown's plugin interface with Vite-specific hooks.
## Basic Structure
```ts
function myPlugin(): Plugin {
return {
name: 'my-plugin',
// hooks...
}
}
```
## Vite-Specific Hooks
### config
Modify config before resolution:
```ts
const plugin = () => ({
name: 'add-alias',
config: () => ({
resolve: {
alias: { foo: 'bar' },
},
}),
})
```
### configResolved
Access final resolved config:
```ts
const plugin = () => {
let config: ResolvedConfig
return {
name: 'read-config',
configResolved(resolvedConfig) {
config = resolvedConfig
},
transform(code, id) {
if (config.command === 'serve') { /* dev */ }
},
}
}
```
### configureServer
Add custom middleware to dev server:
```ts
const plugin = () => ({
name: 'custom-middleware',
configureServer(server) {
server.middlewares.use((req, res, next) => {
// handle request
next()
})
},
})
```
Return function to run **after** internal middlewares:
```ts
configureServer(server) {
return () => {
server.middlewares.use((req, res, next) => {
// runs after Vite's middlewares
})
}
}
```
### transformIndexHtml
Transform HTML entry files:
```ts
const plugin = () => ({
name: 'html-transform',
transformIndexHtml(html) {
return html.replace(/<title>(.*?)<\/title>/, '<title>New Title</title>')
},
})
```
Inject tags:
```ts
transformIndexHtml() {
return [
{ tag: 'script', attrs: { src: '/inject.js' }, injectTo: 'body' },
]
}
```
### handleHotUpdate
Custom HMR handling:
```ts
handleHotUpdate({ server, modules, timestamp }) {
server.ws.send({ type: 'custom', event: 'special-update', data: {} })
return [] // empty = skip default HMR
}
```
## Virtual Modules
Serve virtual content without files on disk:
```ts
const plugin = () => {
const virtualModuleId = 'virtual:my-module'
const resolvedId = '\0' + virtualModuleId
return {
name: 'virtual-module',
resolveId(id) {
if (id === virtualModuleId) return resolvedId
},
load(id) {
if (id === resolvedId) {
return `export const msg = "from virtual module"`
}
},
}
}
```
Usage:
```ts
import { msg } from 'virtual:my-module'
```
Convention: prefix user-facing path with `virtual:`, prefix resolved id with `\0`.
## Plugin Ordering
Use `enforce` to control execution order:
```ts
{
name: 'pre-plugin',
enforce: 'pre', // runs before core plugins
}
{
name: 'post-plugin',
enforce: 'post', // runs after build plugins
}
```
Order: Alias → `enforce: 'pre'` → Core → User (no enforce) → Build → `enforce: 'post'` → Post-build
## Conditional Application
```ts
{
name: 'build-only',
apply: 'build', // or 'serve'
}
// Function form:
{
apply(config, { command }) {
return command === 'build' && !config.build.ssr
}
}
```
## Universal Hooks (from Rolldown)
These work in both dev and build:
- `resolveId(id, importer)` - Resolve import paths
- `load(id)` - Load module content
- `transform(code, id)` - Transform module code
```ts
transform(code, id) {
if (id.endsWith('.custom')) {
return { code: compile(code), map: null }
}
}
```
## Client-Server Communication
Server to client:
```ts
configureServer(server) {
server.ws.send('my:event', { msg: 'hello' })
}
```
Client side:
```ts
if (import.meta.hot) {
import.meta.hot.on('my:event', (data) => {
console.log(data.msg)
})
}
```
Client to server:
```ts
// Client
import.meta.hot.send('my:from-client', { msg: 'Hey!' })
// Server
server.ws.on('my:from-client', (data, client) => {
client.send('my:ack', { msg: 'Got it!' })
})
```
<!--
Source references:
- https://vite.dev/guide/api-plugin
-->
@@ -0,0 +1,108 @@
---
name: vite-environment-api
description: Vite 6+ Environment API for multiple runtime environments
---
# Environment API (Vite 6+)
The Environment API formalizes multiple runtime environments beyond the traditional client/SSR split.
## Concept
Before Vite 6: Two implicit environments (`client` and `ssr`).
Vite 6+: Configure as many environments as needed (browser, node server, edge server, etc.).
## Basic Configuration
For SPA/MPA, nothing changes—options apply to the implicit `client` environment:
```ts
export default defineConfig({
build: { sourcemap: false },
optimizeDeps: { include: ['lib'] },
})
```
## Multiple Environments
```ts
export default defineConfig({
build: { sourcemap: false }, // Inherited by all environments
optimizeDeps: { include: ['lib'] }, // Client only
environments: {
// SSR environment
server: {},
// Edge runtime environment
edge: {
resolve: { noExternal: true },
},
},
})
```
Environments inherit top-level config. Some options (like `optimizeDeps`) only apply to `client` by default.
## Environment Options
```ts
interface EnvironmentOptions {
define?: Record<string, any>
resolve?: EnvironmentResolveOptions
optimizeDeps: DepOptimizationOptions
consumer?: 'client' | 'server'
dev: DevOptions
build: BuildOptions
}
```
## Custom Environment Instances
Runtime providers can define custom environments:
```ts
import { customEnvironment } from 'vite-environment-provider'
export default defineConfig({
environments: {
ssr: customEnvironment({
build: { outDir: '/dist/ssr' },
}),
},
})
```
Example: Cloudflare's Vite plugin runs code in `workerd` runtime during development.
## Backward Compatibility
- `server.moduleGraph` returns mixed client/SSR view
- `ssrLoadModule` still works
- Existing SSR apps work unchanged
## When to Use
- **End users**: Usually don't need to configure—frameworks handle it
- **Plugin authors**: Use for environment-aware transformations
- **Framework authors**: Create custom environments for their runtime needs
## Plugin Environment Access
Plugins can access environment in hooks:
```ts
{
name: 'env-aware',
transform(code, id, options) {
if (options?.ssr) {
// SSR-specific transform
}
},
}
```
<!--
Source references:
- https://vite.dev/guide/api-environment
- https://vite.dev/blog/announcing-vite6
-->
@@ -0,0 +1,157 @@
---
name: vite-rolldown
description: Vite 8 Rolldown bundler and Oxc transformer migration
---
# Rolldown Migration (Vite 8)
Vite 8 replaces esbuild+Rollup with Rolldown, a unified Rust-based bundler.
## What Changed
| Before (Vite 7) | After (Vite 8) |
|-----------------|----------------|
| esbuild (dev transform) | Oxc Transformer |
| esbuild (dep pre-bundling) | Rolldown |
| Rollup (production build) | Rolldown |
| `rollupOptions` | `rolldownOptions` |
| `esbuild` option | `oxc` option |
## Performance Impact
- 10-30x faster than Rollup for production builds
- Matches esbuild's dev performance
- Unified behavior between dev and build
## Config Migration
### rollupOptions → rolldownOptions
```ts
// Before (Vite 7)
export default defineConfig({
build: {
rollupOptions: {
external: ['vue'],
output: { globals: { vue: 'Vue' } },
},
},
})
// After (Vite 8)
export default defineConfig({
build: {
rolldownOptions: {
external: ['vue'],
output: { globals: { vue: 'Vue' } },
},
},
})
```
### esbuild → oxc
```ts
// Before (Vite 7)
export default defineConfig({
esbuild: {
jsxFactory: 'h',
jsxFragment: 'Fragment',
},
})
// After (Vite 8)
export default defineConfig({
oxc: {
jsx: {
runtime: 'classic',
pragma: 'h',
pragmaFrag: 'Fragment',
},
},
})
```
### JSX Configuration
```ts
export default defineConfig({
oxc: {
jsx: {
runtime: 'automatic', // or 'classic'
importSource: 'react', // for automatic runtime
},
jsxInject: `import React from 'react'`, // auto-inject
},
})
```
### Custom Transform Targets
```ts
export default defineConfig({
oxc: {
include: ['**/*.ts', '**/*.tsx'],
exclude: ['node_modules/**'],
},
})
```
## Plugin Compatibility
Most Vite plugins work unchanged. Rolldown supports Rollup's plugin API.
If a plugin only works during build:
```ts
{
...rollupPlugin(),
enforce: 'post',
apply: 'build',
}
```
## New Capabilities
Rolldown unlocks features not possible before:
- Full bundle mode (experimental)
- Module-level persistent cache
- More flexible chunk splitting
- Module Federation support
## Gradual Migration
For large projects, migrate via `rolldown-vite` first:
```bash
# Step 1: Test with rolldown-vite
pnpm add -D rolldown-vite
# Replace vite import in config
import { defineConfig } from 'rolldown-vite'
# Step 2: Once stable, upgrade to Vite 8
pnpm add -D vite@8
```
## Overriding Vite in Frameworks
When framework depends on older Vite:
```json
{
"pnpm": {
"overrides": {
"vite": "8.0.0"
}
}
}
```
<!--
Source references:
- https://vite.dev/blog/announcing-vite8-beta
- https://vite.dev/blog/announcing-vite7
- https://vite.dev/config/shared-options#oxc
-->
+1
View File
@@ -0,0 +1 @@
../../.agents/skills/accessibility
+1
View File
@@ -0,0 +1 @@
../../.agents/skills/frontend-design
+1
View File
@@ -0,0 +1 @@
../../.agents/skills/nodejs-backend-patterns
+1
View File
@@ -0,0 +1 @@
../../.agents/skills/nodejs-best-practices
+1
View File
@@ -0,0 +1 @@
../../.agents/skills/seo
+1
View File
@@ -0,0 +1 @@
../../.agents/skills/svelte-code-writer
+1
View File
@@ -0,0 +1 @@
../../.agents/skills/svelte5-best-practices
+1
View File
@@ -0,0 +1 @@
../../.agents/skills/tailwind-css-patterns
+1
View File
@@ -0,0 +1 @@
../../.agents/skills/typescript-advanced-types
+1
View File
@@ -0,0 +1 @@
../../.agents/skills/vite
+55
View File
@@ -0,0 +1,55 @@
{
"version": 1,
"skills": {
"accessibility": {
"source": "addyosmani/web-quality-skills",
"sourceType": "autoskills-registry",
"computedHash": "bffe3d08cfe92ebad63699f74ce29e35c19850ebfbf474c1463183cfe34d6a09"
},
"frontend-design": {
"source": "anthropics/skills",
"sourceType": "autoskills-registry",
"computedHash": "82fb11a63fb1e35ee2469516ed02d54695f783115b1540c0e783197af4240a3a"
},
"nodejs-backend-patterns": {
"source": "wshobson/agents",
"sourceType": "autoskills-registry",
"computedHash": "710a5e6f83c46e8f6c43356df55c143a7375c4414559a581654ce51709138c55"
},
"nodejs-best-practices": {
"source": "sickn33/antigravity-awesome-skills",
"sourceType": "autoskills-registry",
"computedHash": "7361ab02fb6b09913e3bdd9cf61c629ed6c17de9485e6a781054e5d437ccfc29"
},
"seo": {
"source": "addyosmani/web-quality-skills",
"sourceType": "autoskills-registry",
"computedHash": "c184da724d1c61ad077f27418ea8e7e88fd54bcdf98165e18be7e4681cbd5e20"
},
"svelte-code-writer": {
"source": "sveltejs/ai-tools",
"sourceType": "autoskills-registry",
"computedHash": "b5e12f4a6369f9e482d863fd0dde5cfc215041947902dacc416a2f9a50da166a"
},
"svelte5-best-practices": {
"source": "ejirocodes/agent-skills",
"sourceType": "autoskills-registry",
"computedHash": "89df0819dcaf5292635267d6e4262f274845c0efa3db7bc4c2a0d5aac2431117"
},
"tailwind-css-patterns": {
"source": "giuseppe-trisciuoglio/developer-kit",
"sourceType": "autoskills-registry",
"computedHash": "8fd534b64b3f305ade80c207e2f9ba4338d5e409e355af9f724c36764b6709c1"
},
"typescript-advanced-types": {
"source": "wshobson/agents",
"sourceType": "autoskills-registry",
"computedHash": "5ca0e177c6aaaba1889255691224daafdb7d71f317cc70bede1590d3907ded42"
},
"vite": {
"source": "antfu/skills",
"sourceType": "autoskills-registry",
"computedHash": "e0b52fe3ffea533e1e543ac8a8a20fccca3beed9f8bdc31a0934fe9f554439e8"
}
}
}