| name | add-hook |
| description | Step-by-step guide for adding a new React hook to the shadcn-hooks project. Use when the user wants to create, add, or implement a new hook in this repository. |
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:
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:
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": [
"@shadcnhooks/use-dependency-a",
"@shadcnhooks/use-dependency-b"
]
}
- List all hooks/libs this hook depends on, prefixed with
@shadcnhooks/
- If no dependencies:
{ "registryDependencies": [] }
1c. demo/demo-01.tsx — Demo component
'use client'
import { use<Name> } from '..'
export function Demo01() {
}
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 (see use-boolean which includes use-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 in afterEach
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
AUTO for standalone hooks
- Use
EXTERNAL for 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 run pnpm run sync or pnpm --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
sync command from this skill. The user will run sync manually after reviewing the changes.
packages/shadcn-hooks/src/index.ts exports are also auto-managed — no manual editing needed.