Style Binding Support In Jay-Html
Style Binding Support in Jay-HTML
Written for AI agents. See Log Methodology Note below for details.
Current State
The runtime fully supports dynamic binding into style properties, as evidenced by the test in /packages/runtime/runtime/test/lib/element.test.ts:
e('div', {
textContent: dp((vs) => vs.text),
style: {
color: dp((vs) => vs.color),
width: dp((vs) => vs.width),
},
});
This runtime structure allows individual CSS properties to be bound dynamically using dp() (dynamic property), enabling reactive style updates.
The Problem
The jay-html compiler does not support dynamic style bindings. The current implementation in jay-html-compiler.ts (lines 128-133) treats all style attributes as static CSS text:
if (attrCanonical === 'style')
renderedAttributes.push(
new RenderFragment(`style: {cssText: '${attributes[attrName].replace(/'/g, "\\'")}'}`),
);
This means jay-html cannot express reactive styles like:
<div style="color: {color}; width: {width}px"></div>
Why This Matters
- Design Tool Integration: Design tools may want to bind dynamic values to style properties (colors, sizes, visibility, etc.)
- Consistency: Other attributes support binding (class, value, disabled, etc.), but styles do not
- Component Flexibility: Components need reactive styling based on view state without resorting to class-based workarounds
High-Level Design
Jay-HTML Syntax
Support inline binding expressions within style attribute values:
<div style="color: {color}; width: {width}; background: {bg}"></div>
Mixed static and dynamic:
<div style="margin: 10px; color: {textColor}; padding: {spacing}px"></div>
Compilation Strategy
- Parse style attribute: Split CSS text into individual property declarations
- Detect bindings: For each property, check if the value contains
{...}expressions - Generate runtime code:
- Static properties: Use plain string values
- Dynamic properties: Wrap in
dp()with appropriate expression - Generate
style: { prop: value, ... }object instead ofstyle: {cssText: '...'}
Example Transformation
Input jay-html:
<div style="color: {color}; width: 100px; opacity: {isVisible?1:0}"></div>
Generated code:
e('div', {
style: {
color: dp((vs) => vs.color),
width: '100px',
opacity: dp((vs) => (vs.isVisible ? 1 : 0)),
},
});
Edge Cases to Handle
- All static styles: Keep current
cssTextoptimization for fully static styles - CSS property name normalization: Convert kebab-case to camelCase (e.g.,
background-color→backgroundColor) - Units: Handle cases where units are part of the binding (e.g.,
{width}px) - Escaping: Handle quotes and special characters in static portions
- Empty/invalid styles: Gracefully handle malformed CSS
Implementation Notes
- Reuse existing expression parsing infrastructure (
parsePropertyExpression) - CSS parsing can be simple splitting on
;then:(no need for full CSS parser) - Consider performance: fully static styles should use
cssTextfor efficiency - Tests needed in
compiler-jay-htmlpackage to validate both static and dynamic style compilation
Implementation Status
Completed ✅
Implementation Details
The implementation adds a new PEG.js parser rule styleDeclarations that properly parses CSS declarations, handling:
- Simple accessors:
{color},{width} - Template strings:
{fontSize}pxgenerates`${vs.fontSize}px` - Complex expressions through the template parser
- Kebab-case to camelCase conversion for CSS properties
Code Changes
PEG.js Parser (/lib/expressions/expression-parser.pegjs):
- Added
styleDeclarationsrule - Top-level entry point for parsing style strings - Added
styleDeclarationrule - Parses individualproperty: valuepairs - Added
stylePropNamerule - Matches CSS property names (including kebab-case) - Added
styleValueContentrule - Parses values with template string support - Added
styleValueStringrule - Matches static CSS value text
Expression Compiler (/lib/expressions/expression-compiler.ts):
- Added
StyleDeclarationinterface - Represents a parsed CSS declaration - Added
StyleDeclarationsinterface - Contains all declarations and dynamic flag - Added
parseStyleDeclarations()function - Entry point for parsing style strings
Jay-HTML Compiler (/lib/jay-target/jay-html-compiler.ts):
- Added
renderStyleAttribute()- Uses pegjs parser to process style strings - Updated
renderAttributes()- Delegates style attribute handling to the new function
Optimization
The implementation preserves the cssText optimization for fully static styles, only generating style objects when at least one property is dynamic.
Test Coverage
Test: /test/fixtures/basics/style-bindings/style-bindings.jay-html
Validates:
- Fully dynamic styles
- Mixed static and dynamic properties
- Kebab-case property conversion
- Template string values (e.g.,
{fontSize}px) - Static style optimization
Unit Tests: /test/expressions/expression-compiler.unit.test.ts
Added comprehensive parseStyleDeclarations test suite covering:
- Fully static styles
- Fully dynamic styles
- Mixed static and dynamic styles
- Template string values
- Kebab-case to camelCase conversion
- Trailing semicolons (single and multiple)
- CSS comments (
/* ... */) - Complex CSS functions (e.g.,
linear-gradient,rgba) - Whitespace variations
- Empty declarations
These tests ensure the PEG.js parser correctly handles real-world CSS including:
- Complex gradient functions with nested parentheses
- RGB/RGBA color values with commas
- CSS comments within declarations
- Multiple consecutive semicolons
- Properties with hyphens (converted to camelCase)
Example Output
// Fully dynamic
e('div', { style: { color: dp((vs) => vs.color), width: dp((vs) => vs.width) } });
// Mixed static/dynamic
e('div', { style: { margin: '10px', color: dp((vs) => vs.color), padding: '20px' } });
// Kebab-case conversion + template string
e('div', {
style: { backgroundColor: dp((vs) => vs.color), fontSize: dp((vs) => `${vs.fontSize}px`) },
});
// Fully static (optimized)
e('div', { style: { cssText: 'background: red; padding: 10px' } });
Robustness
The PEG.js parser handles complex real-world CSS including:
- ✅ CSS comments (
/* ... */) - stripped during parsing - ✅ Complex functions with nested parentheses (e.g.,
linear-gradient(rgba(...), rgba(...))) - ✅ Color values with commas (e.g.,
rgb(223, 229, 235)) - ✅ URLs with quoted strings and special characters (e.g.,
url('/images/I2:2069;2:1758_FILL.png')) - ✅ Single and double quoted strings in values
- ✅ Multiple consecutive semicolons
- ✅ Empty declarations
- ✅ Whitespace variations
- ✅ Mixed kebab-case and camelCase properties
Tested with production Figma-exported styles containing:
- 20+ properties
- CSS comments
- Complex gradient functions
- URLs with colons and special characters
- Quoted strings with various characters
Future Considerations
- CSS-in-JS style objects:
style="{styleObject}"to pass entire style object - CSS custom properties:
style="--theme-color: {color}"for CSS variables - Animation/transition support: May need special handling for timing values
- Ternary operators in style values: Current expression parser doesn't support ternary in property expressions (only in class expressions)
Log Methodology Note
Note: These design logs are written primarily for AI agents as part of the Design Log methodology and made accessible here for human readers. The language and structure are optimized for machine consumption — expect precise, specification-style prose rather than narrative documentation.