add-hook
Add Hook
Guide for adding a new hook to the shadcn-hooks project. The source of truth is src/registry/hooks/. The packages/shadcn-hooks/ directory is auto-generated and should NOT be edited manually. Do NOT run any sync command as part of this skill; the user will run it manually when needed.
Checklist
Copy this checklist and track progress:
Add Hook: use-<name>
- [ ] Step 1: Create registry entry (src/registry/hooks/use-<name>/)
- [ ] Step 2: Register in meta.json
- [ ] Step 3: Update introduction.mdx
- [ ] Step 4: Create skill reference doc
- [ ] Step 5: Update skill SKILL.md function table
- [ ] Step 6: Verify (lint + test)
Step 1: Create Registry Entry
Create directory src/registry/hooks/use-<name>/ with 5 files:
1a. index.ts — Hook implementation
Rules:
- Use
@/registry/hooks/...and@/registry/lib/...import aliases - Named export:
export function use<Name>(...) - Export all public types from the same file
- TypeScript strict, no
any - Reuse existing hooks where possible (e.g.
useBoolean,useEventListener,useMemoizedFn)
Reference example — simple hook:
// src/registry/hooks/use-previous/index.ts
import { useRef } from 'react'
export type ShouldUpdateFunc<T> = (prev?: T, next?: T) => boolean
const defaultShouldUpdate = <T>(a?: T, b?: T) => !Object.is(a, b)
export default function usePrevious<T>(
state: T,
shouldUpdate: ShouldUpdateFunc<T> = defaultShouldUpdate,
): T | undefined {
const prevRef = useRef<T>()
const curRef = useRef<T>()
if (shouldUpdate(curRef.current, state)) {
prevRef.current = curRef.current
curRef.current = state
}
return prevRef.current
}
Reference example — hook composing other hooks:
// src/registry/hooks/use-hover/index.ts
import { useBoolean } from '@/registry/hooks/use-boolean'
import { useEventListener } from '@/registry/hooks/use-event-listener'
import type { BasicTarget } from '@/registry/lib/create-effect-with-target'
export interface UseHoverOptions {
onEnter?: () => void
onLeave?: () => void
onChange?: (isHovering: boolean) => void
}
export function useHover(
target: BasicTarget,
options?: UseHoverOptions,
): boolean {
const { onEnter, onLeave, onChange } = options || {}
const [state, { setTrue, setFalse }] = useBoolean(false)
useEventListener(
'mouseenter',
() => {
onEnter?.()
setTrue()
onChange?.(true)
},
{ target },
)
useEventListener(
'mouseleave',
() => {
onLeave?.()
setFalse()
onChange?.(false)
},
{ target },
)
return state
}
1b. registry-item.json — Dependencies
{
"registryDependencies": ["@hooks/use-dependency-a", "@hooks/use-dependency-b"]
}
- List all hooks/libs this hook depends on, prefixed with
@hooks/ - If no dependencies:
{ "registryDependencies": [] }
1c. demo/demo-01.tsx — Demo component
'use client'
import { use<Name> } from '..'
export function Demo01() {
// Interactive demo showcasing the hook
}
Rules:
- Must be a client component (
'use client') - Import hook from
'..'(parent directory) - Use shadcn UI components from
~/components/ui/...(e.g.Button,Card,Input) - Demo should be interactive and clearly show the hook's behavior
1d. index.mdx — Documentation page
---
title: use<Name>
description: A hook to <description>
---
import { Demo01 } from './demo/demo-01'
<Demo01 />
## Installation
<Tabs items={['CLI', 'Manual']}>
<Tab>
<InstallCLI value='use-<name>' />
</Tab>
<Tab>
Copy and paste the following code into your project.
<RegistrySourceCode value='use-<name>' />
</Tab>
</Tabs>
## API
\`\`\`ts
// Full type declarations here
\`\`\`
## Credits
- [use-<name>](credit-link)
Notes:
- If the hook has internal dependencies, add multiple
<RegistrySourceCode>entries under the Manual tab (seeuse-booleanwhich includesuse-toggle) - The API section should contain full TypeScript type declarations
1e. index.test.ts — Tests
import { act, renderHook } from '@testing-library/react'
import { describe, expect, it } from 'vitest'
import { use<Name> } from './index'
describe('use<Name>', () => {
it('should ...', () => {
const { result } = renderHook(() => use<Name>(...))
expect(result.current).toBe(...)
})
})
Rules:
- Use
vitest+@testing-library/react - Import from
'./index' - Cover: default behavior, state changes, edge cases, callbacks
- For DOM-based hooks, create elements in
beforeEach/ clean up inafterEach
Step 2: Register in meta.json
Add "use-<name>" to src/registry/hooks/meta.json under the appropriate category:
---State---— state management hooks---Advanced---— memoization, refs, advanced patterns---Lifecycle---— effects, timers, mount/unmount---Browser---— DOM, events, browser APIs---Dev---— development/debug utilities---External---— wrappers around external libraries
Step 3: Update Introduction Page
Add the new hook to content/docs/introduction.mdx under the appropriate category section:
- State Management — state management hooks
- Advanced — memoization, refs, advanced patterns
- Lifecycle & Effects — effects, timers, mount/unmount
- Browser — DOM, events, browser APIs
- Dev — development/debug utilities
- External Libraries — wrappers around external libraries
Add a line in alphabetical order within the category:
- [`use<Name>`](/docs/hooks/use-<name>) - <Short description>
Step 4: Create Skill Reference
Create skills/shadcn-hooks/references/use<Name>.md:
# use<Name>
<One-line description>.
## Usage
\`\`\`tsx
import { use<Name> } from '@/hooks/use-<name>'
function Component() {
// Minimal usage example
}
\`\`\`
## Type Declarations
\`\`\`ts
// Full type signatures
\`\`\`
## Parameters
| Parameter | Type | Default | Description |
| --------- | ---- | ------- | ----------- |
| ... | ... | ... | ... |
## Returns
| Type | Description |
| ---- | ----------- |
| ... | ... |
Step 5: Update Skill Function Table
Add an entry to the appropriate category table in skills/shadcn-hooks/SKILL.md:
| [`use<Name>`](references/use<Name>.md) | <description> | AUTO |
- Use
AUTOfor standalone hooks - Use
EXTERNALfor hooks that wrap external npm packages
Step 6: Verify
- Run linter: check edited files for lint errors
- Run tests:
pnpm test run src/registry/hooks/use-<name>/index.test.ts - Verify the dev server renders the doc page correctly (if running)
- Do not modify
packages/shadcn-hooks/and do not runpnpm run syncorpnpm --dir packages/shadcn-hooks sync; leave that step to the user
Notes
packages/shadcn-hooks/is auto-generated. Never edit files in that directory manually as part of this skill.- Do not execute any
synccommand from this skill. The user will run sync manually after reviewing the changes. packages/shadcn-hooks/src/index.tsexports are also auto-managed — no manual editing needed.