Vitus Labs
Hooks

Accessibility & SSR Hooks

useColorScheme, useReducedMotion, useIsomorphicLayoutEffect, useUpdateEffect.

useColorScheme

Returns the user's OS-level color scheme preference. Reacts to system changes in real-time.

import { useColorScheme } from '@vitus-labs/hooks'

function App() {
  const scheme = useColorScheme()  // 'light' or 'dark'

  return (
    <Provider theme={scheme === 'dark' ? darkTheme : lightTheme}>
      <Layout />
    </Provider>
  )
}

Returns

'light' | 'dark' — based on prefers-color-scheme media query.

Pairs well with rocketstyle's mode prop:

<Provider theme={theme} mode={useColorScheme()}>
  <App />
</Provider>

useReducedMotion

Returns true when the user has enabled "prefers-reduced-motion" in their OS settings.

import { useReducedMotion } from '@vitus-labs/hooks'

function AnimatedCard({ children }) {
  const reducedMotion = useReducedMotion()

  return (
    <div
      style={{
        animation: reducedMotion ? 'none' : 'slideIn 0.3s ease',
        transition: reducedMotion ? 'none' : 'all 0.2s ease',
      }}
    >
      {children}
    </div>
  )
}

Returns

booleantrue when prefers-reduced-motion: reduce matches.

Use this to:

  • Disable or simplify animations
  • Skip auto-playing videos
  • Reduce parallax effects
  • Provide static alternatives to animated content

useIsomorphicLayoutEffect

Uses useLayoutEffect on the client and useEffect on the server. Avoids the React SSR warning about useLayoutEffect during server rendering.

import { useIsomorphicLayoutEffect } from '@vitus-labs/hooks'

function MeasuredComponent() {
  const ref = useRef<HTMLDivElement>(null)
  const [height, setHeight] = useState(0)

  useIsomorphicLayoutEffect(() => {
    if (ref.current) {
      setHeight(ref.current.getBoundingClientRect().height)
    }
  }, [])

  return <div ref={ref}>Height: {height}px</div>
}

Parameters

Same as useLayoutEffect / useEffect:

ParameterTypeDescription
effectEffectCallbackEffect function
depsDependencyListOptional dependency array

useUpdateEffect

Like useEffect but skips the initial mount — only fires on updates.

import { useUpdateEffect } from '@vitus-labs/hooks'

function FilteredList({ filters }) {
  const [results, setResults] = useState(initialResults)

  // Only re-fetch when filters change, NOT on initial mount
  useUpdateEffect(() => {
    fetchResults(filters).then(setResults)
  }, [filters])

  return <List data={results} />
}

Parameters

Same as useEffect:

ParameterTypeDescription
effectEffectCallbackEffect function
depsDependencyListOptional dependency array

Common Use Cases

// Log only when value changes (not on mount)
useUpdateEffect(() => {
  analytics.track('value-changed', { value })
}, [value])

// Show "unsaved changes" only after initial render
useUpdateEffect(() => {
  setHasUnsavedChanges(true)
}, [formData])

// Sync external state only on updates
useUpdateEffect(() => {
  externalStore.update(localState)
}, [localState])

On this page