`dropShadow({ blur: … })` is silently ignored, though the `shadow` shorthand and the `blur` prop both call that value the blur

Author: danikobeissiCreated Sep 20, 2026Updated Sep 20, 2026

What happens

Writing blur in an effect helper does nothing, and nothing says so. The value is dropped, the default radius of 8 is drawn, and the render succeeds without a warning.

What makes this a trap rather than a simple naming preference is that the dialect itself uses that word for this value in two other places:

  • the shadow string shorthand is x y blur color, and its third value becomes the shadow's radius;
  • there is a top-level blur prop, which is a layer blur's radius.

So someone who has written shadow="4 4 12 #000" — or who knows CSS box-shadow, where the third value is the blur radius — writes dropShadow({ x: 4, y: 4, blur: 12, color: '#000' }) and gets a shadow with a radius of 8. The name the dialect taught them is the one name the helper ignores.

Underneath it is a general silence: the helpers build their effect by reading the option names they know, so any other key is read by nobody and reported by nobody. A typo behaves the same way — colour, offsetY, radious all vanish without a word.

TypeScript does not catch this. ShadowEffectOptions would reject an unknown key in a compiled call site, but the dialect is not compiled by the caller: render.ts transforms the JSX with sucrase and evaluates it with new Function, passing the helpers in as __helpers. The object literal reaching dropShadow is built at runtime, from text a user or an agent wrote, so the excess-property check never runs. Runtime is the only place this can be caught, and nothing catches it there.

This matters more than a typo elsewhere would, because the output is a picture. The render looks plausible, and the difference between a 12px and an 8px shadow is not something a caller checks. A value silently replaced by a default is indistinguishable from a value that was never written.

Reproduction

  1. In OpenPencil, render this design JSX:

    <Frame w={100} h={60} bg="#FFD23F" effects={[dropShadow({ x: 4, y: 4, blur: 12, color: '#14101F' })]} />
    
  2. Read the frame back — through get_node, or through the MCP server, or by exporting the design. The effect's radius is 8, the default, not 12.

  3. No warning appears anywhere in the result.

  4. The same value under the name the helper knows does what it says:

    <Frame w={100} h={60} bg="#FFD23F" effects={[dropShadow({ x: 4, y: 4, radius: 12, color: '#14101F' })]} />

    gives radius: 12.

  5. For the shorthand that teaches the other name, on the same frame: shadow="4 4 12 #14101F" gives radius: 12.

  6. A plain typo behaves like step 1: dropShadow({ colour: '#FF0000' }) draws the default black shadow and says nothing.

What was expected

An option a caller writes is either applied, or reported by name. Either the helpers accept blur as a name for the shadow's radius — which would match the shadow shorthand, the blur prop and CSS — or they warn that blur was ignored and name the options that exist; ideally both. The same for any key the helper does not know.

Where

  • packages/core/src/design-jsx/effects.tsshadowEffect at lines 31–42 builds the effect from named options only; blurEffect at lines 44 onwards does the same, keeping radius and visible. The helpers are dropShadow, innerShadow, layerBlur, backgroundBlur and foregroundBlur.
  • The two places the dialect calls this value a blur: packages/core/src/design-jsx/props-overrides.ts — the shadow shorthand at lines 560–576, whose parts[2] becomes radius, and the blur prop at lines 578–590.
  • Where the options stop being typed: packages/core/src/design-jsx/render.ts at line 101.
  • Seen at commit e2de247.