Skip to content

JSX Syntax ​

Gea uses JSX that is close to standard HTML. The Vite plugin transforms it into HTML template strings at build time — there is no createElement or virtual DOM at runtime.

Attributes ​

GeaHTML equivalentNotes
class="foo"class="foo"Use class, not className
class={`btn ${active ? 'on' : ''}`}Dynamic classTemplate literal for dynamic classes
value={text}value="..."For input elements
checked={bool}checkedFor checkboxes
disabled={bool}disabledFor buttons/inputs
aria-label="Close"aria-label="Close"ARIA attributes pass through

Event Attributes ​

Both native-style (click, change) and React-style (onClick, onChange) event attribute names are supported. Native-style is preferred by convention.

jsx
<button click={handleClick}>Click</button>
<input input={handleInput} />
<input change={handleChange} />
<input keydown={handleKeyDown} />
<input blur={handleBlur} />
<input focus={handleFocus} />
<span dblclick={handleDoubleClick}>Text</span>

Supported events: click, dblclick, input, change, keydown, keyup, blur, focus, mousedown, mouseup, submit, dragstart, dragend, dragover, dragleave, drop.

With @geajs/mobile: tap, longTap, swipeRight, swipeUp, swipeLeft, swipeDown.

Event handlers receive the native DOM event:

jsx
const handleInput = e => {
  store.setName(e.target.value)
}

Spread Attributes ​

Spread an object onto an HTML element to set several attributes at once:

jsx
const { children, ...rest } = this.props
return <button class="btn" {...rest}>{children}</button>

Keys are applied in source order: a spread overrides the attributes written before it, and attributes written after it override the spread. The spread stays live. When the object changes, changed keys are written and keys that are gone are removed. on* keys in any letter case (and bare event names like click) become event handlers; one whose value is not a function is ignored. class, style, value, visible and boolean attributes work as they do when written directly. children, key, ref, dangerouslySetInnerHTML and keys that are not valid attribute names are ignored. No key is assigned as an element property: innerHTML in the object is an ordinary innerhtml attribute. Spread values for URL attributes (href, src, action, formaction and the others geaSanitizeAttr lists) go through geaSanitizeAttr, which empties a javascript:, vbscript: or non-image data: URL. This includes a URL attribute written before the spread on the same element, since the spread applies it too. srcdoc is skipped, and so are attributeName, to, from, by and values on SVG animation elements (animate, set, animateMotion, animateTransform); on other elements, such as values on feColorMatrix, a spread sets them as usual.

Differences from React ​

FeatureGeaReact
CSS classesclass="foo"className="foo"
Event handlersclick={fn} or onClick={fn}onClick={fn}
Input eventsinput={fn} or onInput={fn}onChange={fn}
Keyboard eventskeydown={fn} or onKeyDown={fn}onKeyDown={fn}
Checked inputschecked={bool} + change={fn}checked={bool} + onChange={fn}

Text Interpolation ​

Use curly braces for dynamic content:

jsx
<span>{count}</span>
<span>{user.name}</span>
<span>{activeCount} {activeCount === 1 ? 'item' : 'items'} left</span>

Style Objects ​

Gea supports inline style objects with camelCase property names, like React:

jsx
// Constant style object
<div style={{ backgroundColor: 'red', fontSize: '14px', fontWeight: 'bold' }}>
  Styled content
</div>

// Dynamic — updated per property at runtime
<div style={{ color: this.textColor, opacity: this.isVisible ? 1 : 0 }}>
  Dynamic styling
</div>

Numeric style-object values receive px for dimensional properties: a style object containing height: 120 produces height: 120px. Unitless properties such as opacity and lineHeight, custom properties (--*), and zero do not receive added units. Explicit strings keep their units. This is Gea runtime behavior, not a requirement of JSX syntax.

String styles are also supported and passed through as-is:

jsx
<div style="color:red">Static string</div>
<div style={`width:${size}px`}>Dynamic string</div>

Property names use camelCase: backgroundColor → background-color, fontSize → font-size.

ref Attribute ​

Use ref to get a direct reference to a DOM element after render:

jsx
export default class Canvas extends Component {
  canvasEl = null

  template() {
    return (
      <div class="wrapper">
        <canvas ref={this.canvasEl} width="800" height="600"></canvas>
      </div>
    )
  }

  onAfterRender() {
    const ctx = this.canvasEl.getContext('2d')
    ctx.fillRect(0, 0, 100, 100)
  }
}

The compiler generates a setup method that assigns the DOM element to the specified property after each render. Multiple refs are supported. For the component's root element, use this.el instead.

Component Tags ​

Components are referenced by their import name in PascalCase. The Vite plugin converts them to kebab-case custom elements internally and passes props via data-prop-* attributes.

jsx
import TodoItem from './todo-item'

<TodoItem todo={todo} onToggle={() => store.toggle(todo.id)} />

Single-word component names (e.g., Link, Label) are automatically prefixed with gea- to produce valid custom element tag names (e.g., gea-link, gea-label). Multi-word PascalCase names naturally contain a hyphen when converted to kebab-case (e.g., TodoItem becomes todo-item) and are used as-is. This is handled transparently — you always use the PascalCase import name in JSX.

Type Safety ​

Gea provides full JSX type-checking via TypeScript's jsxImportSource mechanism. This means prop autocompletion, type errors on invalid attributes, and hover-to-inspect types work out of the box in any TypeScript-aware editor — VS Code, Cursor, Vim, Zed, etc. No framework-specific editor plugin is needed.

The type system understands:

  • All standard HTML elements and their attributes (inherited from @types/react)
  • Gea-specific attributes: class (instead of className), for (on <label>)
  • Short event names: click, input, change, keydown, etc.
  • Component props declared with declare props on class components (optionally typed inside template() via : this['props'] for full end-to-end type safety) or parameter types on function components

To enable type-checking, your tsconfig.json needs:

json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@geajs/core"
  }
}

Projects scaffolded with create-gea have this configured automatically.

Unsupported Patterns ​

The compiler throws clear errors at build time for these patterns:

PatternFix
Dynamic tag namesUse conditional rendering instead
{() => <div />} (function as child)Use named render prop attributes
export function Foo() { return <div /> }Use export default function
Fragments as .map() item rootsWrap in a single root element

Spreading onto a component tag (<Comp {...props} />) is not supported yet, and it raises no error: the compiler drops the spread, so the component receives none of those props (#198). Pass the props individually.

Released under the MIT License.