Building Better React Apps: A Complete Guide to Custom shadcn/ui Components and Hooks
React development often involves repetitive patterns and boilerplate code. What if you could streamline your workflow with a collection of reusable components and hooks that handle common UI patterns and state management? Today, I'm excited to share a comprehensive registry of custom shadcn/ui components and hooks that will supercharge your React development.
What's in This Registry?
This registry contains 15 carefully crafted utilities split into two categories:
- 8 Utility Components for rendering logic and UI patterns
- 8 Custom Hooks for state management and browser interactions
All components are built with TypeScript, follow React best practices, and integrate seamlessly with the shadcn/ui ecosystem.
🚀 Quick Setup (Recommended)
For the best experience, configure the registry namespace in your components.json:
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "app/globals.css",
"baseColor": "slate",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"hooks": "@/hooks"
},
"registries": {
"@amin": "https://aminahmady.vercel.app/r/{name}.json"
}
}Then install any component using:
pnpm dlx shadcn@latest add @amin/[component-name]🧩 Utility Components
1. Condition - Smart Conditional Rendering
The Condition component provides an elegant way to handle complex conditional rendering scenarios, similar to switch statements but more React-friendly.
import { Condition, If, ElseIf, Else } from "@/components/ui/condition"
function UserStatus({ user }) {
return (
<Condition>
<If condition={user.isAdmin}>
<AdminPanel />
</If>
<ElseIf condition={user.isPremium}>
<PremiumFeatures />
</ElseIf>
<Else>
<BasicFeatures />
</Else>
</Condition>
)
}Installation:
# Using namespace (recommended)
pnpm dlx shadcn@latest add @amin/condition
# Or direct URL
npx shadcn@latest add https://aminahmady.vercel.app/r/condition.jsonKey Features:
- Declarative conditional rendering
- Multiple condition support with
ElseIf - Fallback content with
Else - Type-safe props
2. Show - Simple Conditional Display
For straightforward show/hide logic, the Show component offers a clean alternative to ternary operators.
import { Show } from "@/components/ui/show"
function Notification({ hasError, message }) {
return (
<Show show={hasError}>
<Alert variant="destructive">{message}</Alert>
</Show>
)
}Installation:
# Using namespace (recommended)
pnpm dlx shadcn@latest add @amin/show
# Or direct URL
npx shadcn@latest add https://aminahmady.vercel.app/r/show.json3. For - Declarative List Rendering
The For component makes list rendering more readable and handles edge cases like empty arrays.
import { For } from "@/components/ui/for"
function TodoList({ todos }) {
return (
<For
each={todos}
fallback={<p>No todos yet!</p>}
>
{(todo, index) => (
<TodoItem key={todo.id} todo={todo} index={index} />
)}
</For>
)
}Installation:
# Using namespace (recommended)
pnpm dlx shadcn@latest add @amin/for
# Or direct URL
npx shadcn@latest add https://aminahmady.vercel.app/r/for.jsonFeatures:
- Generic type support
- Fallback content for empty arrays
- Index access in render function
4. Switch/Case - Pattern Matching
Handle multiple conditions elegantly with the Switch and Case components.
import { Switch, Case, Default } from "@/components/ui/switch-case"
function StatusBadge({ status }) {
return (
<Switch value={status}>
<Case value="pending">
<Badge variant="secondary">Pending</Badge>
</Case>
<Case value="approved">
<Badge variant="success">Approved</Badge>
</Case>
<Case value="rejected">
<Badge variant="destructive">Rejected</Badge>
</Case>
<Default>
<Badge variant="outline">Unknown</Badge>
</Default>
</Switch>
)
}Installation:
# Using namespace (recommended)
pnpm dlx shadcn@latest add @amin/switch-case
# Or direct URL
npx shadcn@latest add https://aminahmady.vercel.app/r/switch-case.json5. While - Loop Component
For dynamic content generation based on conditions:
import { While } from "@/components/ui/while"
function Countdown({ seconds }) {
return (
<While condition={() => seconds > 0} maxIterations={60}>
{(iteration) => <div>Time remaining: {seconds - iteration}</div>}
</While>
)
}Installation:
# Using namespace (recommended)
pnpm dlx shadcn@latest add @amin/while
# Or direct URL
npx shadcn@latest add https://aminahmady.vercel.app/r/while.json6. DoWhile - Do-While Loop Component
For loops that execute at least once:
import { DoWhile } from "@/components/ui/do-while"
function LoadingDots() {
return (
<DoWhile condition={true} maxIterations={3}>
{(iteration) => <span key={iteration}>.</span>}
</DoWhile>
)
}Installation:
# Using namespace (recommended)
pnpm dlx shadcn@latest add @amin/do-while
# Or direct URL
npx shadcn@latest add https://aminahmady.vercel.app/r/do-while.json7. Switcher - Binary State Toggle
Perfect for toggling between two different UI states:
import { Switcher } from "@/components/ui/switcher"
function ViewToggle({ isGridView }) {
return (
<Switcher next={isGridView}>
<ListView key="list" />
<GridView key="grid" />
</Switcher>
)
}Installation:
# Using namespace (recommended)
pnpm dlx shadcn@latest add @amin/switcher
# Or direct URL
npx shadcn@latest add https://aminahmady.vercel.app/r/switcher.json8. VisuallyHidden - Accessibility Component
Built on Radix UI primitives for hiding content visually while keeping it accessible to screen readers:
import { VisuallyHidden } from "@/components/ui/visually-hidden"
function IconButton({ icon, label }) {
return (
<button>
{icon}
<VisuallyHidden>{label}</VisuallyHidden>
</button>
)
}Installation:
# Using namespace (recommended)
pnpm dlx shadcn@latest add @amin/visually-hidden
# Or direct URL
npx shadcn@latest add https://aminahmady.vercel.app/r/visually-hidden.json🎣 Custom Hooks
1. useBoolean - Boolean State Management
Simplify boolean state with convenient helper methods:
import { useBoolean } from "@/hooks/use-boolean"
function Modal() {
const { value: isOpen, setTrue: open, setFalse: close, toggle } = useBoolean()
return (
<>
<Button onClick={open}>Open Modal</Button>
<Dialog open={isOpen} onOpenChange={close}>
{/* Modal content */}
</Dialog>
</>
)
}Installation:
# Using namespace (recommended)
pnpm dlx shadcn@latest add @amin/use-boolean
# Or direct URL
npx shadcn@latest add https://aminahmady.vercel.app/r/use-boolean.jsonFeatures:
setTrue(),setFalse(),toggle()helpers- Type-safe boolean operations
- Memoized callbacks for performance
2. useCounter - Numeric State Management
Perfect for counters, pagination, and numeric controls:
import { useCounter } from "@/hooks/use-counter"
function LikeButton() {
const { count, increment, decrement, reset } = useCounter(0)
return (
<div>
<Button onClick={decrement}>-</Button>
<span>{count}</span>
<Button onClick={increment}>+</Button>
<Button onClick={reset}>Reset</Button>
</div>
)
}Installation:
# Using namespace (recommended)
pnpm dlx shadcn@latest add @amin/use-counter
# Or direct URL
npx shadcn@latest add https://aminahmady.vercel.app/r/use-counter.json3. useCountdown - Timer Functionality
Build timers and countdown features easily:
import { useCountdown } from "@/hooks/use-countdown"
function CountdownTimer() {
const [count, { startCountdown, stopCountdown, resetCountdown }] = useCountdown({
countStart: 60,
countStop: 0,
intervalMs: 1000
})
return (
<div>
<div>Time: {count}s</div>
<Button onClick={startCountdown}>Start</Button>
<Button onClick={stopCountdown}>Stop</Button>
<Button onClick={resetCountdown}>Reset</Button>
</div>
)
}Installation:
# Using namespace (recommended)
pnpm dlx shadcn@latest add @amin/use-countdown
# Or direct URL
npx shadcn@latest add https://aminahmady.vercel.app/r/use-countdown.jsonFeatures:
- Configurable intervals
- Start/stop/reset controls
- Increment or decrement modes
- Custom stop conditions
4. useEventListener - Event Management
Type-safe event listeners with automatic cleanup:
import { useEventListener } from "@/hooks/use-event-listener"
function KeyboardShortcuts() {
useEventListener('keydown', (event) => {
if (event.ctrlKey && event.key === 's') {
event.preventDefault()
saveDocument()
}
})
return <div>Press Ctrl+S to save</div>
}Installation:
# Using namespace (recommended)
pnpm dlx shadcn@latest add @amin/use-event-listener
# Or direct URL
npx shadcn@latest add https://aminahmady.vercel.app/r/use-event-listener.json5. useInterval - Timer Hook
Manage intervals with React lifecycle integration:
import { useInterval } from "@/hooks/use-interval"
function LiveClock() {
const [time, setTime] = useState(new Date())
useInterval(() => {
setTime(new Date())
}, 1000)
return <div>{time.toLocaleTimeString()}</div>
}Installation:
# Using namespace (recommended)
pnpm dlx shadcn@latest add @amin/use-interval
# Or direct URL
npx shadcn@latest add https://aminahmady.vercel.app/r/use-interval.json6. useIsMobile - Responsive Design
Detect mobile devices and respond to viewport changes:
import { useIsMobile } from "@/hooks/use-mobile"
function ResponsiveLayout() {
const isMobile = useIsMobile()
return isMobile ? <MobileLayout /> : <DesktopLayout />
}Installation:
# Using namespace (recommended)
pnpm dlx shadcn@latest add @amin/use-mobile
# Or direct URL
npx shadcn@latest add https://aminahmady.vercel.app/r/use-mobile.json7. useScript - Dynamic Script Loading
Load external scripts dynamically:
import { useScript } from "@/hooks/use-script"
function GoogleMaps() {
useScript(
'https://maps.googleapis.com/maps/api/js?key=YOUR_KEY',
'head',
{ async: true }
)
return <div id="map">Loading map...</div>
}Installation:
# Using namespace (recommended)
pnpm dlx shadcn@latest add @amin/use-script
# Or direct URL
npx shadcn@latest add https://aminahmady.vercel.app/r/use-script.json8. useIsMounted - Mount State Detection
Check if a component is mounted to prevent state updates on unmounted components (SSR-safe):
import { useIsMounted } from "@/hooks/use-is-mounted"
function ConditionalComponent() {
const isMounted = useIsMounted()
if (!isMounted) return <div>Loading...</div>
return <div>Component is mounted and ready!</div>
}Installation:
# Using namespace (recommended)
pnpm dlx shadcn@latest add @amin/use-is-mounted
# Or direct URL
npx shadcn@latest add https://aminahmady.vercel.app/r/use-is-mounted.json9. useIsomorphicLayoutEffect - SSR-Safe Layout Effect
A drop-in replacement for useLayoutEffect that works safely in SSR environments:
import { useIsomorphicLayoutEffect } from "@/hooks/use-isomorphic-layout-effect"
function DOMManipulation() {
const ref = useRef<HTMLDivElement>(null)
useIsomorphicLayoutEffect(() => {
if (ref.current) {
// Safe DOM manipulation
ref.current.style.height = '100px'
}
}, [])
return <div ref={ref}>Styled element</div>
}Installation:
# Using namespace (recommended)
pnpm dlx shadcn@latest add @amin/use-isomorphic-layout-effect
# Or direct URL
npx shadcn@latest add https://aminahmady.vercel.app/r/use-isomorphic-layout-effect.json10. useSsr - Environment Detection
Detect server/client environment for conditional rendering:
import { useSsr } from "@/hooks/use-ssr"
function EnvironmentAwareComponent() {
const { isBrowser, isServer } = useSsr()
return (
<div>
{isBrowser && <ClientOnlyFeature />}
{isServer && <ServerOnlyContent />}
</div>
)
}Installation:
# Using namespace (recommended)
pnpm dlx shadcn@latest add @amin/use-ssr
# Or direct URL
npx shadcn@latest add https://aminahmady.vercel.app/r/use-ssr.jsonInstallation:
# Using namespace (recommended)
pnpm dlx shadcn@latest add @amin/use-is-mounted
pnpm dlx shadcn@latest add @amin/use-isomorphic-layout-effect
pnpm dlx shadcn@latest add @amin/use-ssr
# Or direct URLs
npx shadcn@latest add https://aminahmady.vercel.app/r/use-is-mounted.json
npx shadcn@latest add https://aminahmady.vercel.app/r/use-isomorphic-layout-effect.json
npx shadcn@latest add https://aminahmady.vercel.app/r/use-ssr.json🚀 Installation & Setup
Prerequisites
Make sure you have shadcn/ui set up in your project:
npx shadcn-ui@latest initAdding Components to Your Project
You can add these components to your project using the shadcn CLI in two ways: direct URL installation or by configuring a custom registry namespace.
Prerequisites
Make sure you have shadcn/ui set up in your project:
npx shadcn@latest init🎯 Best Practices
1. Type Safety
All components and hooks are built with TypeScript and provide full type safety:
// Type-safe iteration
<For each={users}>
{(user) => <UserCard user={user} />} // user is properly typed
</For>2. Performance Optimization
Hooks use useCallback and useMemo where appropriate to prevent unnecessary re-renders:
const { toggle } = useBoolean() // toggle is memoized3. Accessibility
Components like VisuallyHidden ensure your app remains accessible:
<Button>
<Icon />
<VisuallyHidden>Save document</VisuallyHidden>
</Button>4. SSR Compatibility
All hooks handle server-side rendering gracefully:
const { isBrowser, isServer } = useSsr()
// Safe to use in Next.js, Remix, etc.🔧 Customization
Extending Components
All components accept standard React props and can be easily extended:
// Custom styling
<Show show={isVisible} className="animate-fade-in">
<Alert>Custom styled alert</Alert>
</Show>
// Adding custom logic
const CustomFor = ({ children, each, sortBy }) => {
const sortedItems = useMemo(() =>
sortBy ? [...each].sort(sortBy) : each, [each, sortBy]
)
return <For each={sortedItems}>{children}</For>
}Hook Composition
Combine hooks for complex state management:
function useToggleCounter() {
const { value: isEnabled, toggle } = useBoolean()
const { count, increment, reset } = useCounter()
const incrementIfEnabled = useCallback(() => {
if (isEnabled) increment()
}, [isEnabled, increment])
return { isEnabled, toggle, count, incrementIfEnabled, reset }
}🚦 Migration Guide
From Standard React Patterns
Before:
{items.length > 0 ? (
items.map((item, index) => (
<Item key={item.id} item={item} />
))
) : (
<EmptyState />
)}After:
<For each={items} fallback={<EmptyState />}>
{(item, index) => <Item key={item.id} item={item} />}
</For>Before:
const [isOpen, setIsOpen] = useState(false)
const open = () => setIsOpen(true)
const close = () => setIsOpen(false)After:
const { value: isOpen, setTrue: open, setFalse: close } = useBoolean()🎉 Conclusion
This registry provides a solid foundation for building React applications with better developer experience and cleaner code. Each component and hook solves real-world problems while maintaining the flexibility and composability that makes React great.
Key Benefits:
- 🎯 Focused: Each utility has a single responsibility
- 🔒 Type-Safe: Full TypeScript support
- ♿ Accessible: Built with accessibility in mind
- 🚀 Performance: Optimized for React's rendering cycle
- 🔧 Flexible: Easy to customize and extend
- 📱 Responsive: Mobile and SSR ready
Start small by adding just the components you need, and gradually build up your custom UI library. Your future self (and your team) will thank you for the cleaner, more maintainable code.
Ready to supercharge your React development? Pick a component, install it, and start building better UIs today!
📚 Additional Resources
- shadcn/ui Documentation
- React TypeScript Best Practices
- Accessibility Guidelines
- React Performance Optimization
Tags: #React #TypeScript #shadcn #UI #Components #Hooks #Frontend #WebDevelopment