Skip to content

ordered-list marker crashes the app when list.markerColor normalizes to null #868

Description

@davidhsing

iOS: ordered-list marker crashes the app when list.markerColor normalizes to null ('', var(--x), oklch(...), …)

Environment

react-native-enriched-markdown 1.1.0-nightly-20260921-971111abf (we originally hit this on 1.0.2 stable)
react-native 0.86.3
expo 57.0.24
react 19.2.8
Platform iOS (native crash; reproduced on iOS Simulator)

Summary

If list.markerColor is a string that @react-native/normalize-colors cannot parse, it becomes null and is forwarded to the native side as-is. For ordered lists, the native marker drawer builds an NSDictionary literal with that value, so a nil color throws:

*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[1]

The JS layer already added a default for the "prop omitted" case, but an explicitly provided unparseable value still reaches native. Since design-token pipelines commonly emit modern CSS color syntax (oklch(...), color-mix(...)), this is easy to hit in practice.

Steps to reproduce

import { EnrichedMarkdownText } from 'react-native-enriched-markdown';

<EnrichedMarkdownText
    markdown={'1. first\n2. second'}            // an ordered list is required
    markdownStyle={{ list: { markerColor: 'var(--text-primary)' } }}
/>

Any of these values triggers it:

'' · 'var(--x)' · 'oklch(60% 0.2 250)' · 'color-mix(in oklab, red 50%, blue)' · 'lab(50% 40 59.5)' · 'color(display-p3 1 0 0)'

Actual

*** Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]:
attempt to insert nil object from objects[1]'

Expected

An unparseable color should be ignored (fall back to the default marker color) instead of crashing the app.

Root cause

ios/utils/ListMarkerDrawer.m:194

NSDictionary *mAttrs = @{NSFontAttributeName : font,
                         NSForegroundColorAttributeName : [_config listStyleMarkerColor]};

No nil guard: objects[1] is the color, so a nil color always throws. Note this is the only place in the library that reads _config listStyleMarkerColor through a dictionary literal.

Contrast with the bullet path in the same file (drawBulletAtX:centerY:depth:), which uses [[_config listStyleMarkerColor] setFill] — nil-tolerant, which is why unordered lists don't crash while ordered lists do.

On the JS side the default only covers the "not provided" case:

// src/normalizeMarkdownStyle.ts:162
markerColor: normalizeColor('#6B7280')!,

The ! is a tell: the value may be undefined/null, and it is forwarded regardless. Public typing doesn't prevent it either — markdownStyle.list.markerColor?: string — and the native prop is typed markerColor: ColorValue.

What normalizeColor actually accepts (measured, RN 0.86.3)

input result
#6B7280, #fff, rgba(0,0,0,0.5), rgb(1 2 3 / 0.5), red, transparent, hwb(120 0% 0%) valid ✓
'' null ✗
var(--x) null ✗
oklch(60% 0.2 250) null ✗
color-mix(in oklab, red 50%, blue) null ✗
lab(50% 40 59.5) null ✗
color(display-p3 1 0 0) null ✗

(measurement: require('@react-native/normalize-colors') called with each of these strings)

This is the part that makes the bug non-edge-case: Tailwind/uniwind-style themes are defined with oklch() and generate color-mix() derivatives, so a token read straight from the theme is a perfectly valid CSS color that this pipeline turns into a native crash.

Suggested fix

  1. Native (covers every input)

    UIColor *markerColor = [_config listStyleMarkerColor] ?: UIColor.labelColor;
    NSDictionary *mAttrs = @{NSFontAttributeName : font,
                             NSForegroundColorAttributeName : markerColor};

    (or insert conditionally via an NSMutableDictionary)

  2. JS (fail fast at the boundary) — resolve the fallback inside the internal style object instead of forwarding null to native.

  3. Worth auditing — ios/input/ENRMInputListMarkerDrawer.mm:112,124 uses the same dictionary shape with a different value source; same class of risk.

Workaround on our side

We only pass colors that normalizeColor can parse (design tokens resolved to hex/rgb) and never pass '' or var(--x). It works, but it's a footgun: oklch(...) is a legitimate CSS color that a design system will hand you without warning.

Additional context

  • Only the ordered list path crashes (bullets are safe), so the markdown body needs at least one OL item.
  • Web is unaffected — normalizeMarkdownStyle.web.ts doesn't normalize, the string goes straight to CSS.
  • Happy to open a PR with the native nil-coalesce above if that's preferred.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions