This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
React Chrono is a modern, flexible timeline component library for React that supports multiple layout modes (horizontal, vertical, vertical-alternating, horizontal-all) with rich media support, theming, and accessibility features. Built with TypeScript, Vanilla Extract CSS, and tested with Vitest + Playwright.
Compatibility: React 18.2+ and React 19+ | Node.js 22+ | Modern browsers (Chrome, Firefox, Safari, Edge)
pnpm run dev # Start dev server with hot reload (site preview)
pnpm run build # Production build (generates dist/)
pnpm run build:analyze # Build with bundle analysisDemo & Examples:
- Demo site code:
src/demo/(runs withpnpm run dev) - Example components:
src/examples/ - CodesandBox example:
codesandbox-example/
# Unit Tests (Vitest)
pnpm test # Run unit tests in watch mode
pnpm test -- --run # Run tests once without watch mode
pnpm test timeline # Run tests matching "timeline" in filename
pnpm run test-ui # Run tests with Vitest UI
# E2E Testing (Playwright) - Cross-browser (Chromium, Firefox, WebKit)
pnpm run test:e2e # Run all E2E tests (all browsers)
pnpm run test:e2e:ui # Run E2E with Playwright UI (interactive debugging)
pnpm run test:e2e:headed # Run E2E in headed mode (visible browser)
pnpm run test:e2e:debug # Run E2E in debug mode (step-through)
pnpm run test:e2e:chrome # Run E2E in Chromium only
pnpm run test:e2e:firefox # Run E2E in Firefox only
pnpm run test:e2e:webkit # Run E2E in WebKit (Safari) only
# Component Tests (Playwright) - Cross-browser isolation testing
pnpm run test:ct # Component tests with Playwright
pnpm run test:ct:ui # Component tests with Playwright UI
# Integration Tests - Tests built package in real browser
pnpm run test:integration # Run integration tests (validates dist/ works)
pnpm run test:integration:ui # Run integration tests with UI
pnpm run test:integration:headed # Run integration tests in headed mode
# Test Utilities
pnpm run test:install # Install Playwright browsers (required once)
pnpm run test:report # View Playwright HTML test report
pnpm run test:trace # View Playwright trace (for debugging failures)
pnpm run test:codegen # Record interactions as test code
pnpm run test:update-snapshots # Update visual snapshots
# CI Testing
pnpm run test:e2e:ci # Run E2E with GitHub Actions reporter
pnpm run test:e2e:shard # Run sharded tests (parallel across machines)Cross-Browser Testing:
- All Playwright E2E tests run on Chromium, Firefox, and WebKit by default
- Integration tests run on Chromium and Firefox (WebKit optional due to longer build times)
- Component tests run on all three browsers for comprehensive coverage
- GitHub Actions workflow (
.github/workflows/playwright.yml) runs full cross-browser matrix automatically
- Run
pnpm run test:installonce before running any E2E tests to install Playwright browser binaries - This is mandatory and takes a few minutes to complete
- After installation, all E2E, integration, and component tests will work across all browsers
Test Organization:
- E2E Tests (
tests/e2e/): Test complete user workflows in demo site - Integration Tests (
tests/integration/): Validate built package works in real apps - Component Tests (
tests/components/): Test individual components in isolation - Test Fixtures (
tests/fixtures/): Shared helpers and utilities - Test Coverage: Accessibility, keyboard navigation, nested timelines, media support, performance
pnpm run eslint # Lint TypeScript files
pnpm run fix-js # Auto-fix ESLint issues
pnpm run format # Format code with Prettier
pnpm run lint # Check Prettier formatting
pnpm run lint:all # Run all linting (ESLint + CSS + Prettier)
pnpm run clean # Format and lint everything
pnpm run find-bugs # TypeScript check + lint + test
pnpm run build:analyze # Analyze bundle size (verify < 250 KB limit)
# Unit Test Debugging
pnpm run test-ui # Interactive Vitest UI (watch mode with visual browser)
pnpm test -- --reporter=verbose # Run tests with detailed outputThe project is transitioning from styled-components to Vanilla Extract CSS-in-TypeScript for better performance and type safety.
Style File Patterns:
*.css.tsfiles contain Vanilla Extract styles (modern approach)- Imports use
import { className } from './file.css'(note.cssextension, not.css.ts) - Components use Vanilla Extract recipes for variants and compound styles
- Legacy styled-components still exist in some files but should be avoided in new code
When writing/editing styles:
- Use Vanilla Extract for all new components
- Leverage the sprinkles utility system in
src/styles/system/sprinkles.css.ts - Use recipes from
@vanilla-extract/recipesfor component variants - Import design tokens from
src/styles/tokens/index.css.ts
The project uses a single unified context (TimelineContextProvider) instead of multiple nested contexts.
Key files:
src/components/contexts/TimelineContextProvider.tsx- Main context providersrc/components/contexts/index.tsx- ExportsuseTimelineContext()hook
Context structure:
// Three logical groups in the context:
TimelineStaticConfig // Rarely changing configuration (mode, cardHeight, cardWidth, etc.)
TimelineDynamicState // Frequently changing state (isDarkMode, isMobile, textContentDensity)
TimelineMemoizedObjects // Computed/memoized values (theme, buttonTexts, i18nHelper)Usage patterns:
// Full context (use sparingly - subscribes to all changes)
import { useTimelineContext } from '../contexts';
const { theme, mode, isDarkMode, toggleDarkMode } = useTimelineContext();
// Performance-optimized selective subscriptions:
import { useTimelineStaticConfig } from '../contexts';
const { mode, cardHeight } = useTimelineStaticConfig(); // Rarely re-renders
import { useTimelineDynamicState } from '../contexts';
const { isDarkMode, toggleDarkMode } = useTimelineDynamicState(); // Frequently re-renders
import { useTimelineMemoizedObjects } from '../contexts';
const { theme, buttonTexts } = useTimelineMemoizedObjects(); // Computed valuesThe project uses custom hooks extensively to separate concerns:
Location: src/hooks/
Key hooks (hierarchical composition):
useTimelineNavigation.ts- Main orchestrator - Composes navigation hooks and coordinates next/previous/first/last navigationuseTimelineItemNavigation.ts- Manages active item state and click eventsuseTimelineKeyboardNavigation.ts- Handles keyboard events (Arrow keys, Home, End)useTimelineScrolling.ts- Calculates scroll positions and target elements
useTimelineScroll.ts- Scroll behavior and positioninguseTimelineSearch.ts- Search functionality with match highlightinguseTimelineMode.ts- Timeline mode switching logicuseTimelineMedia.ts- Handles media loading and lazy-loading lifecycleuseFullscreen.ts- Cross-browser fullscreen API with vendor prefixesuseI18n.ts- Internationalization support with text resolutionuseFocusManager.ts- Focus trap and management for modals/popoversuseMeasureHeight.ts- Reactive element height measurement
Pattern: Hooks return stable references using useCallback and useMemo to prevent unnecessary re-renders. Major hooks compose smaller specialized hooks for modularity.
Component hierarchy:
src/components/index.tsx- Root Chrono component - Handles prop migration, validation, and wraps with context/error boundarysrc/components/timeline/timeline.tsx- Main Timeline orchestrator - Coordinates hooks, navigation, and focus managementsrc/components/timeline/TimelineView.tsx- Layout renderer - Delegates to mode-specific implementationssrc/components/timeline-horizontal/- Horizontal mode implementationsrc/components/timeline-vertical/- Vertical/alternating mode implementation
src/components/timeline/timeline-toolbar.tsx- Toolbar with controls
Reusable elements:
src/components/elements/- Generic UI elements (popover, list, icons)src/components/timeline-elements/- Timeline-specific components (cards, points, media)timeline-card/- Card containers with media supporttimeline-card-content/- Content sections (header, body, footer)timeline-card-media/- Media display (images, videos, YouTube)
src/components/toolbar/- Toolbar componentssrc/components/fonts/- Font provider and Google Fonts integration
Critical pattern for fullscreen compatibility:
When using ReactDOM.createPortal(), the component must detect fullscreen mode and portal to the fullscreen element instead of document.body:
// Detect fullscreen element with vendor prefix support
const getFullscreenElement = () => {
return document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement ||
document.msFullscreenElement ||
null;
};
// Portal to fullscreen element when active, otherwise document.body
const portalContainer = getFullscreenElement() || document.body;
ReactDOM.createPortal(content, portalContainer);This pattern is essential because browser fullscreen APIs only render the fullscreen element and its descendants. See src/components/elements/popover/index.tsx for reference implementation.
The project uses path aliases configured in tsconfig.json:
import { Theme } from '@models/Theme';
import { getUniqueID } from '@utils/index';
import { useTimelineContext } from '@components/contexts';
import { useFullscreen } from '@hooks/useFullscreen';Available aliases:
@utils/*→src/utils/*@models/*→src/models/*@effects/*→src/effects/*(legacy effects/hooks)@components/*→src/components/*@hooks/*→src/hooks/*
The project supports dynamic Google Fonts loading with per-element control:
Location: src/components/fonts/
Features:
- Dynamic font loading with intelligent caching
- Per-element font weight and style control
- Automatic fallback handling
- Font provider wraps timeline for global availability
Configuration:
<Chrono
style={{
fontFamily: {
googleFont: {
name: 'Inter',
weights: [400, 600, 700],
styles: ['normal', 'italic']
}
}
}}
/>The project supports both legacy and new grouped API for backward compatibility:
Old API (deprecated but still works):
<Chrono
borderLessCards={true}
disableNavOnKey={false}
timelinePointDimension={18}
/>New Grouped API (preferred):
<Chrono
display={{ borderless: true }}
interaction={{ keyboardNavigation: true }}
layout={{ pointSize: 18 }}
/>Automatic Migration System:
- Located in
src/utils/propMigration.ts - Root component (
src/components/index.tsx) detects which API is used migrateLegacyProps()automatically converts old → new formatwarnDeprecatedProps()logs development warnings for deprecated props- All internal components work exclusively with the new format
When adding new props:
- Add to the appropriate group in
src/models/TimelinePropsV2.ts(layout, interaction, content, display, media, animation, style, accessibility, i18n) - Update prop migration mapping in
src/utils/propMigration.tsfor backward compatibility - Add deprecation warning if replacing an old prop
- Bundler: Vite for development and production builds
- Target: ES2022 with React 18.2+ and React 19+ support
- Externals: React and React-DOM are peer dependencies (not bundled)
- Output: ESM (
dist/index.esm.js), CJS (dist/index.cjs), Types (dist/types/index.d.ts), CSS (dist/style.css) - Entry Points:
src/react-chrono.ts(library entry) exports fromsrc/components/index.tsx(root component) - Bundle Size Limit: 250 KB for both ESM and CJS outputs
- Node Version: Requires Node.js 22+ (specified in package.json engines)
- Unit tests: Vitest with @testing-library/react (prefer
@testing-library/reactover enzyme) - E2E tests: Playwright with cross-browser testing (Chrome, Firefox, Safari, Edge)
- Component tests: Playwright component testing for isolated behavior
- Integration tests: Playwright integration tests for build output validation
- Test location: Co-located with source files in
__tests__/subdirectories - Run specific test:
pnpm test <pattern>(e.g.,pnpm test timelineruns tests matching "timeline")
- Modern browsers: Chrome, Firefox, Safari, Edge
- Mobile: iOS Safari, Chrome Mobile
- No IE11 support (explicitly excluded in browserslist)
- All interactive elements must have proper ARIA labels
- Keyboard navigation is critical (arrow keys, Home/End, Enter, Escape)
- Focus management must be explicit and logical (use
useFocusManagerhook for focus trapping) - Respect
prefers-reduced-motionfor animations - Maintain WCAG AA contrast ratios (4.5:1) in themes
- The project has comprehensive i18n support via
useI18nhook - Customize UI text through the
i18nprop group with 40+ configurable text elements - Located in
src/models/TimelineI18n.tsfor type definitions - Text resolution handled by
src/utils/textResolver.ts - Supports template strings with variable interpolation
- Create component in appropriate directory under
src/components/ - Use Vanilla Extract for styling (create
*.css.tsfile) - Add TypeScript types/models in
src/models/ - Create test file in
__tests__/subdirectory - Export from relevant
index.tsif needed
If a component doesn't appear in fullscreen mode:
- Check if it uses
ReactDOM.createPortal() - Ensure it portals to the fullscreen element when active (see section 5 above)
- Add fullscreen event listeners to update portal container
- Test in multiple browsers (Chrome, Safari, Firefox have different implementations)
- Create
component-name.css.tsfile - Import design tokens from
src/styles/tokens/ - Use sprinkles for common utilities (
src/styles/system/sprinkles.css.ts) - Use recipes from
@vanilla-extract/recipesfor component variants - Import with
.cssextension:import { className } from './component.css'(NOT.css.ts) - For responsive styles, use sprinkles conditions:
{ mobile: 'block', desktop: 'flex' } - Runtime theme CSS variables are handled by
src/styles/theme-bridge.ts
- Create hook in
src/hooks/directory - Use
useCallbackanduseMemofor stable references - Add comprehensive unit tests
- Document return types with TypeScript
- Export from
src/hooks/index.ts
- Unit tests: Check
coverage/directory for coverage reports - E2E tests: Use
pnpm run test:e2e:uifor interactive debugging - View traces:
pnpm run test:tracefor Playwright traces - Update snapshots:
pnpm run test:update-snapshotswhen intentional changes occur
Node.js: Version 22 or higher (required by package.json engines)
Package Manager: pnpm (version 10.15.1 or compatible)
The project uses pnpm for faster installs and better dependency management. Do not use npm or yarn.
ALWAYS import Vanilla Extract styles with .css extension (not .css.ts):
// ✅ Correct
import { wrapper } from './component.css';
// ❌ Wrong
import { wrapper } from './component.css.ts';Use selective context hooks to prevent unnecessary re-renders:
// ❌ Avoid - subscribes to all context changes
const context = useTimelineContext();
// ✅ Prefer - only subscribes to static config
const { mode, cardHeight } = useTimelineStaticConfig();Any component using ReactDOM.createPortal() MUST detect and portal to fullscreen element:
const fullscreenEl = getFullscreenElement();
const container = fullscreenEl || document.body;
ReactDOM.createPortal(content, container);When adding/modifying props, always update both:
src/models/TimelinePropsV2.ts- New grouped APIsrc/utils/propMigration.ts- Legacy prop mapping
Always use useCallback and useMemo in custom hooks to prevent downstream re-renders:
const stableFunction = useCallback(() => { /* ... */ }, [deps]);
const memoizedValue = useMemo(() => computeValue(), [deps]);- Components use
React.memo()for render optimization - Context values are memoized with comprehensive dependency tracking
- Use selective context subscriptions (
useTimelineStaticConfig,useTimelineDynamicState) to prevent unnecessary re-renders - Hooks return stable references via
useCallbackanduseMemo - Media loading is lazy with IntersectionObserver
- Item comparison uses hash-based diffing for efficiency
src/react-chrono.ts- Main library entry point (exports)src/components/index.tsx- Root Chrono component (prop migration and validation)src/components/timeline/timeline.tsx- Main Timeline orchestratorsrc/components/contexts/TimelineContextProvider.tsx- Unified context systemsrc/utils/propMigration.ts- Legacy to new props conversionsrc/models/TimelinePropsV2.ts- New grouped props interfacesrc/models/TimelineI18n.ts- i18n type definitionsconfig/build/vite.config.mts- Build configurationtsconfig.json- TypeScript configuration with path aliases