Skip to content

getWebStyles returns empty when CSS is injected after CSSListener.initialize() #467

@joernroeder

Description

@joernroeder

Description

After upgrading from 1.2.2 to 1.6.0, useResolveClassNames('bg-background') returns empty on first render when the Tailwind CSS stylesheet is injected into <head> after CSSListener.initialize() runs.

Use Case

We use useResolveClassNames to read Tailwind color values and inject them into React Navigation's ThemeProvider. This lets us drive the navigator's background, border, and card colors from our Tailwind theme:

const { backgroundColor } = useResolveClassNames('bg-background')
const { borderTopColor: borderColor } = useResolveClassNames('border-border')

return {
  ...currentTheme,
  colors: {
    ...currentTheme.colors,
    ...(backgroundColor ? { background: backgroundColor.toString() } : undefined),
    ...(borderColor ? { border: borderColor.toString() } : undefined),
  },
}

When useResolveClassNames returns empty, the navigator falls back to React Navigation's DefaultTheme.colors.background (rgb(242, 242, 242)). Since the theme provider never re-renders with the resolved value, the gray background persists.

Root Cause

The getWebStyles implementation changed between versions:

  • 1.2.2: Sets className on a dummy div, reads getComputedStyle() directly. Always works because the browser resolves CSS regardless of when stylesheets load.
  • 1.6.0: Iterates CSSListener.activeRules to find matching rules, then reads computed properties only from those. If CSSListener.initialize() runs before the CSS is injected into <head>, the rules are missed.

The MutationObserver does catch later additions, but scheduleInitialization uses requestIdleCallback (50ms timeout) — useResolveClassNames reads before that fires.

Expected Behavior

useResolveClassNames should return the correct resolved styles on first render, matching the 1.2.2 behavior.

Workaround

We hardcode the correct fallback colors in our theme definitions so the empty return is masked. The useResolveClassNames override still applies once CSSListener catches up.

Environment

  • uniwind 1.6.0 (worked in 1.2.2)
  • Expo SDK 55, Metro bundler
  • Web (React Navigation + Expo Router)
  • Typical Expo setup, no custom CSS injection timing

This issue was written by Claude Code on behalf of the developer.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions