typescript
82 lines · 1 tab
Maya Patel
Jan 2026
1 tab
import { useEffect, useRef, ReactNode } from 'react'
import { createPortal } from 'react-dom'
interface ModalProps {
isOpen: boolean
onClose: () => void
title: string
children: ReactNode
closeOnBackdrop?: boolean
}
export function Modal({ isOpen, onClose, title, children, closeOnBackdrop = true }: ModalProps) {
const modalRef = useRef<HTMLDivElement>(null)
const previousFocusRef = useRef<HTMLElement | null>(null)
useEffect(() => {
if (isOpen) {
previousFocusRef.current = document.activeElement as HTMLElement
// Focus first focusable element
const firstFocusable = modalRef.current?.querySelector<HTMLElement>(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
)
firstFocusable?.focus()
// Prevent body scroll
document.body.style.overflow = 'hidden'
return () => {
document.body.style.overflow = ''
previousFocusRef.current?.focus()
}
}
}, [isOpen])
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape' && isOpen) {
onClose()
}
}
document.addEventListener('keydown', handleEscape)
return () => document.removeEventListener('keydown', handleEscape)
}, [isOpen, onClose])
if (!isOpen) return null
return createPortal(
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50"
onClick={(e) => {
if (closeOnBackdrop && e.target === e.currentTarget) {
onClose()
}
}}
>
<div
ref={modalRef}
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
className="bg-white rounded-lg shadow-xl max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto"
>
<div className="flex items-center justify-between p-6 border-b">
<h2 id="modal-title" className="text-xl font-bold">
{title}
</h2>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600"
aria-label="Close dialog"
>
<i className="fas fa-times text-xl" />
</button>
</div>
<div className="p-6">{children}</div>
</div>
</div>,
document.body
)
}
1 file · typescript
Explain with highlit
Accessible modals require focus management, keyboard navigation, and ARIA attributes. I build a reusable Modal component that traps focus inside when open, moves focus to the first interactive element, and returns it to the trigger on close. The Escape key closes the modal, and clicking the backdrop optionally dismisses it. ARIA attributes like role='dialog', aria-modal='true', and aria-labelledby provide semantic meaning for screen readers. I prevent body scroll when the modal is open and use React Portal to render modals at the document root, avoiding z-index conflicts. This pattern ensures modals work for all users regardless of input method.
Related snips
typescript
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
typescript
reliability
retry
by codesnips
2 tabs
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Semantic HTML Example</title>
Semantic HTML5 elements and accessibility best practices
html
html5
semantics
by Alex Chang
2 tabs
typescript
import axios, { AxiosError } from 'axios'
import { v4 as uuidv4 } from 'uuid'
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api/v1',
timeout: 15000,
Axios API client with interceptors
react
axios
api
by Maya Patel
1 tab
typescript
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface FilterState {
search: string
category: string | null
Zustand for lightweight state management
react
zustand
state-management
by Maya Patel
2 tabs
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Form Validation Example</title>
<style>
HTML forms with validation and accessibility
html
forms
validation
by Alex Chang
1 tab
javascript
import { Application } from "@hotwired/stimulus"
import FormSubmitController from "./controllers/form_submit_controller"
const application = Application.start()
application.debug = false
Disable submit button while Turbo form is submitting
rails
hotwire
stimulus
by codesnips
3 tabs
Share this code
Here's the card — post it anywhere.