* fix(core): let fill text share an auto-layout row Fill frames grow and shrink from a zero flex basis, so fill siblings split a row's free space. Fill text only got flexGrow, and its measure function capped it at its stored width, 100px for new text, so seven fill labels in a 280px row each kept 100px and overflowed. Without a measurer, the fallback pinned that width and set no grow at all. Fill text now uses the same zero basis as fill frames on both paths. * fix(design-jsx): read repeat() and minmax() in grid tracks Track lists were split on whitespace, so columns="repeat(7, 1fr)" became the tracks repeat(7, and 1fr), read as fixed 0px and 1px columns that collapsed the grid. Tokens inside parentheses now stay together, repeat() expands its tracks, minmax() grows like its maximum, and a track the grid cannot express sizes to its content instead of to 0. * refactor(scene-graph): parse CSS grid tracks with postcss-value-parser design-jsx read repeat() and minmax() with a hand-written tokenizer and regexes, while dom-css already parses CSS values with postcss-value-parser. Track lists are now parsed in @open-pencil/scene-graph/css on that library, where both packages can use it, and design-jsx calls it. dom-css's hand-written declaration of the library's types is replaced by the types the library ships, which the shared module needs. The Scene Graph guide records that CSS values are parsed there. * fix(dom-css): read shadows, borders, and lengths with the shared CSS parser dom-css tried each word of a shadow as a color and parseColor turned the leading 0 into black, so a shadow written in the usual order imported black, and the headless runtime split the border shorthand on spaces, which cut rgb(226, 232, 240) apart and also gave a black border. Numbers, colors, shadow lists, and shorthand parts are now parsed in @open-pencil/scene-graph/css on postcss-value-parser. Every shadow layer imports, inset ones as inner shadows, a fully transparent color counts as none, and lengths in units that depend on context, such as % or em, are no longer read as pixels. tryParseColor gives the color or null, and parseColor builds on it. * fix(design-jsx): read the shadow prop as a CSS shadow list The shadow prop was split on spaces, so a color before the lengths or a spread made the shadow black, and only one shadow could be set. It now takes a CSS box-shadow list through the shared parser, and pixel lengths in style props use the shared number parser. A rem grid track now has its size instead of sizing to content. * test: type grid track and shadow fixtures The test type check added in #896 rejects the grid track tests that #866 merged, because their object literals widen sizing to string, so bun run check fails on master. The fixtures are now typed GridTrack and Effect values.
11 KiB
OpenPencil design authoring
This reference describes scene creation, not React DOM output. Use the render tool for JSX strings, or import Frame, Text, and other authoring exports from @open-pencil/design-jsx and render them with renderTree from @open-pencil/core/design-jsx in library code. Library exports are not automatically globals in agent eval; use only the bindings exposed by that execution environment.
Composition and layout
flex="row"/flex="col"enables auto-layout. Use it for content; reserve explicitx/yorposition="absolute"for intentional overlays and artwork. Without layout, children share the origin unless positioned.w/haccept pixels,"hug"(content-sized), or"fill"(available space in a supported layout parent). Use Hug for notes, cards, and long pages instead of guessing heights. Fixed viewport sizes and artwork geometry are intentional exceptions.gapcontrols spacing.p,px,py, andpt/pr/pb/plcontrol padding; longhands override shorthands. There is no margin shorthand.justify="start"|"end"|"center"|"between"controls the primary axis;items="start"|"end"|"center"|"stretch"controls the cross axis. Distribution needs available space:betweencannot create extra room in a Hug container.growdistributes available space. Avoid circular Hug/Fill dependencies and redundant fixed widths on growing children. Keep Fill sizing through intermediate containers that should stretch.- For wrapping text in a column, prefer
w="fill"; fixed-width text can usetextAutoResize="height".maxLines/truncateare intentional truncation, not fixes for accidental overflow. wrapandrowGapenable wrapped flex rows.grid,columns, androwsenable grid (for examplecolumns="1fr 200px 1fr"orcolumns="repeat(7, 1fr)"); tracks arefr, pixel lengths,auto,repeat(), andminmax(), which grows like its maximum. Grid children usecolStart,rowStart,colSpan, androwSpan. The current gridgapshorthand takes precedence overcolumnGapandrowGap.flow="auto"|"ltr"|"rtl"controls container flow; textdircontrols writing direction. Preserve these separately.- Outside auto-layout, and for
position="absolute"children,constraints={{ horizontal, vertical }}sets how a layer follows its parent's resizing, with Figma's values in lowercase:"min","center","max","stretch", or"scale".minW,maxW,minH, andmaxHbound a layer's size.visible={false}hides a layer andlockedlocks it. - Use measured node bounds and the existing
arrangetool for independent artboards. Prefer layout constraints to calculating child coordinates; ordinary JavaScript arithmetic is appropriate when real geometry calculations are needed.
Paint, text, and artwork
bg/fill,stroke, and textcoloraccept colors and supported variable references. Set colors explicitly for predictable contrast.fillsaccepts structured paints; gradient helpers includelinearGradient,radialGradient,angularGradient, anddiamondGradient, each taking an array of stops and optional{ opacity, transform }:fills={[linearGradient([['#3b82f6', 0], ['#8b5cf6', 1]])]}.strokedescribes one stroke, refined bystrokeWidth,strokeAlign("inside","center","outside"),strokeDash,strokeCap, andstrokeJoin.strokestakes several, as objects withcolor,weight,align,dash,cap,join, andvisible;strokeWeights={{ top, right, bottom, left }}sets per-side widths.dashPatternsets the node's own dash pattern, separate from the per-strokestrokeDash.roundedandroundedTL/roundedTR/roundedBL/roundedBRcontrol corners.strokeWidth,opacity,rotate, andblendModecontrol appearance.overflow="hidden"clips content; do not hide accidental text overflow to make a broken layout appear correct.effectsaccepts structured effects such asdropShadow,innerShadow, andlayerBlur.shadowtakes a CSSbox-shadowlist, such asshadow="0 4 8 #0002, inset 0 1 0 #fff", andblura layer blur radius; both are convenient shorthands. Effect helpers takeradius, as Figma's effects do; when a JSX string is rendered, an option a paint or effect helper does not support is reported as a warning.- Text content belongs inside
Text. Usesize,font,weight,italic,lineHeight,letterSpacing,textAlign,textAlignVertical,textDecoration, andtextCase. Verify fonts actually load before judging dimensions; do not assume every font is available. Iconuses an Iconify name, size, and color. Prefer icons to emoji when reliable vector output is needed. Image fills belong on appropriate leaf shapes, not containers whose children must remain visible.- Design JSX props are the portable authoring interface. Some CSS-style aliases are supported, but this is not a browser CSS engine; do not assume arbitrary HTML, classes, or styles work.
Variables and components
- Create document variables before referencing them with
designVar('id-or-name').defineVarsgroups references; it does not create variable collections. - COLOR references work in paint props. FLOAT references work in
w,h,gap, padding, corner radii,strokeWidth,opacity, textsize/fontSize,lineHeight, andletterSpacing. GridcolumnGap/rowGapand wrapped flexrowGapalso support FLOAT references; gridgapoverrides both axis-specific gaps. Use numbers or FLOAT references for these scalar props, not CSS unit strings. - References preserve real graph bindings, not just copied values. Set the intended collection mode on the parent before creating scalar-bound content: initial scalar layout resolves that inherited mode. This does not guarantee automatic scalar layout recomputation after a later mode switch. Verify resulting geometry as well as paint when changing modes. Missing or incorrectly typed scalar variables are errors.
bindmaps supported scene-field paths to variable IDs or references when no shorthand exists. Use semantic tokens consistently rather than declaring unused collections.- A reusable JavaScript function shares source code, not component identity. Use
Component,ComponentSet, andInstancefor editable main components and linked instances. Instanceresolves an existing component throughof,component, orcomponentId. Component-set children namedvariant=Primary, for example, define variants that can be selected when instantiating the set.ComponentandComponentSetacceptproperties, an array of native property definitions (id,name,type,defaultValue).Instanceacceptsproperties, an ID-to-value assignment object. Ordinary nodes do not acceptproperties.- Child
propertyRefsconnect fields to stable property IDs, for example[{ propertyId: 'message', field: 'TEXT' }]. Supported fields areTEXT,VISIBLE, andINSTANCE_SWAP; text and swap references require text and instance nodes respectively. References do not depend on layer names. - Instance assignments use the native string values (including
'true'/'false'for BOOLEAN properties and component IDs for swaps). For exampleInstance({ of: noteId, properties: { message: 'Updated review' } }). Assignments persist through component synchronization; unknown IDs and invalid values fail rather than silently creating inert overrides. Select variants through component-set variant props, not through instance property assignments. - Reuse existing local or library components before recreating them. Keep meaningful text, visibility, and swap properties exposed rather than hand-editing cloned child nodes.
- Explicit instance
w/hreplace the inherited sizing mode on that axis; omitted dimensions retain the main component's sizing. Authored overrides survive component synchronization. Distinguish those placement constraints from the main component's default size, and verify actual bounds in narrower parents. Do not compensate for a sizing mismatch with guessed heights, clipping, or manually positioned siblings.
Verification
Inspect structure and actual rendered output. Node counts and describe diagnostics do not establish visual fidelity. Check wrapping with longer content, narrower containers, component edits, and relevant modes. Resolve overflow and contrast problems at their source. Reuse IDs returned by creation tools rather than repeatedly searching for the same nodes.
The examples below are executed by the authoring-reference tests. Create the named variables before running a variable-bound example.
Content-sized review note
<Frame name="Review note" w={280} h="hug" flex="col" gap={8} p={16} bg="#FFFFFF">
<Text name="Author" size={12} weight="medium" color="#252A31">June Lee</Text>
<Text name="Message" w="fill" size={12} color="#6B7079">Give the date a little more room at the bottom.</Text>
</Frame>
Variable-bound spacing and typography
<Frame name="Bound note" w={280} h="hug" flex="col" gap={designVar('Space/small')} p={designVar('Space/medium')} bg="#FFFFFF">
<Text name="Message" w="fill" size={designVar('Type/body')} lineHeight={designVar('Type/body-leading')} letterSpacing={designVar('Type/body-tracking')} color="#252A31">A note that grows with its content.</Text>
</Frame>
Supported syntax inventory
Generated from the renderer metadata. This inventory lists accepted names, not arbitrary browser CSS support.
Elements: Frame, Text, Rectangle, Ellipse, Line, Star, Polygon, Vector, Group, Section, Component, ComponentSet, Instance, View, Rect, Icon.
Helpers: solid, gradient, linearGradient, radialGradient, angularGradient, diamondGradient, dropShadow, innerShadow, layerBlur, backgroundBlur, foregroundBlur, designVar, defineVars.
Properties: name, key, flex, flow, dir, gap, wrap, rowGap, columnGap, justify, justifyContent, items, align, alignItems, grow, w, h, width, height, minW, maxW, minH, maxH, x, y, top, left, position, constraints, p, padding, px, py, pt, pr, pb, pl, bg, fill, fills, background, backgroundColor, stroke, border, borderColor, strokeWidth, borderWidth, strokeAlign, strokeDash, strokeCap, strokeJoin, dashPattern, strokes, strokeWeights, rounded, borderRadius, roundedTL, roundedTR, roundedBL, roundedBR, cornerRadius, cornerSmoothing, opacity, blendMode, rotate, rotation, overflow, mask, visible, locked, shadow, blur, effects, size, fontSize, font, fontFamily, weight, fontWeight, italic, color, text, characters, content, value, title, textAlign, textAlignHorizontal, textHorizontalAlignment, textAlignVertical, textVerticalAlignment, textAutoResize, lineHeight, letterSpacing, textDecoration, textCase, maxLines, truncate, grid, columns, rows, colStart, rowStart, col, row, colSpan, rowSpan, points, pointCount, innerRadius, label, style, bind, component, componentId, properties, propertyRefs, of.