JSX
Learn how JSX works in Hydroxide
JSX lets you write HTML-like syntax in JavaScript. Hydroxide compiles JSX into optimized DOM operations. If you've used React before, the JSX is very similar with some differences.
Key Differences from React
| Feature | React | Hydroxide |
|---|---|---|
| Class attribute | className | class |
| Event handlers | onClick | on-click |
| Real-time input | onChange | on-input |
| Attribute names | camelCase | lowercase (HTML spec) |
| Inline styles | style={{ color: 'red' }} | style="color: red" |
| Two-way binding | Manual | bind-value, bind-checked |
| Conditionals | Ternary in JSX | if, else-if, else attributes |
| Refs | useRef() | const ref = {} |
HTML-like Syntax
Hydroxide's JSX stays close to HTML. Attribute names match the HTML spec (not camelCase like React):
// React
<div className="card" tabIndex={0} autoFocus />
// Hydroxide (same as HTML)
<div class="card" tabindex={0} autofocus />This means you can copy HTML from anywhere and use it directly. Just remember to self-close void elements:
<input />
<img />
<br />Expressions
Use curly braces to embed JavaScript expressions:
const name = 'Alice'
const items = ['apple', 'banana', 'cherry']
<h1>Hello, {name}!</h1>
<p>You have {items.length} items</p>When the expression contains a reactive, it becomes a reactive binding - which updates automatically when the value changes:
const count = reactive(0)
<p>Count: {count()}</p> // updates when count changesEvent Handlers
Event handlers use the on- prefix with lowercase event names (matching browser events):
// React
<button onClick={handleClick} onMouseEnter={handleHover} />
// Hydroxide
<button on-click={handleClick} on-mouseenter={handleHover} />import { reactive } from 'hydroxide'; function Example() { const count = reactive(0); function increment() { count.set(count() + 1); } // open the console to see the logs // when state is updated, component does not re-render console.log('No renders!') return ( <button on-click={increment} class="primary-button"> count is {count()} </button> ); } export default Example;
Common events: on-click, on-input, on-change, on-submit, on-keydown, on-mouseenter, on-mouseleave, on-focus, on-blur
Coming from React?
React's onChange actually uses the "input" event instead of "change" event which fires when the input loses focus.
To have the same behavior as React, use on-input instead of on-change to listen to real-time updates instead of when the input loses focus.
Two-Way Binding
Use bind-value to sync an input's value with a reactive:
const text = reactive('')
<input type="text" bind-value={text} />
<p>You typed: {text()}</p>import { reactive } from 'hydroxide'; function TextInput() { const text = reactive('Hello, Hydroxide!'); return ( <div class="container"> <input type="text" bind-value={text} placeholder="Enter your text here" /> <p class='preview'>{text() || "Nothing"}</p> </div> ); } export default TextInput;
For checkboxes, use bind-checked:
const checked = reactive(false)
<input type="checkbox" bind-checked={checked} />Conditional Rendering
Use if, else-if, and else attributes for conditional rendering:
const count = reactive(0)
<p if={count() === 0}>Zero</p>
<p else-if={count() < 5}>Less than 5</p>
<p else>5 or more</p>import { reactive } from "hydroxide"; function FizzBuzz() { const count = reactive(0); return ( <div class="container"> <input type="range" min="0" max="100" bind-value={count} /> <h1>{count()}</h1> <p if={count() % 15 === 0} class="result fizzbuzz">FizzBuzz</p> <p else-if={count() % 3 === 0} class="result fizz">Fizz</p> <p else-if={count() % 5 === 0} class="result buzz">Buzz</p> <p else class="result">{count()}</p> </div> ); } export default FizzBuzz;
Only one of the elements renders at a time. When the condition changes, Hydroxide swaps them efficiently.
Refs
Get a reference to a DOM element with the ref attribute. Create an empty object and assign it to the ref attribute of an element.
The DOM node of that element will be assigned to the current property of the object which can accessed safely inside onConnect lifecycle hook of the component
import { onConnect } from 'hydroxide'; function Canvas() { const canvasRef = {}; onConnect(() => { const ctx = canvasRef.current.getContext('2d'); ctx.fillStyle = '#a855f7'; ctx.fillRect(20, 20, 60, 60); ctx.fillStyle = '#22d3ee'; ctx.beginPath(); ctx.arc(130, 50, 30, 0, Math.PI * 2); ctx.fill(); }); return <canvas ref={canvasRef} width="180" height="100" />; } export default Canvas;
Styles
Inline styles use a string, just like HTML:
<div style="background-color: red; font-size: 2rem">Styled content</div>Use template literals for reactive styles:
const hue = reactive(200)
<div style={`background-color: hsl(${hue()}, 70%, 60%)`} />import { reactive } from 'hydroxide'; function App() { const hue = reactive(200); return ( <div class="container"> <div class="box" style={`background-color: oklch(0.7 0.3 ${hue()}); transform: rotate(${hue() / 2}deg)`} /> <input type="range" min="0" max="360" bind-value={hue} /> <p>Hue: {hue()}°</p> </div> ); } export default App;