Components
A component is a function that returns JSX. Props in, JSX out. Lifecycle is managed by signals and effects.
Basic component
function Greeting(props: { name: string }) {
return <h1>Hello, {props.name}!</h1>
}
<Greeting name="World" />Props
Props are passed as a single object argument. Destructure for convenience:
function Button({ label, onClick }: { label: string; onClick: () => void }) {
return <button onclick={onClick}>{label}</button>
}Children
Children come through props.children:
function Card({ children }: { children: any }) {
return <div class="card">{children}</div>
}
<Card>
<h2>Title</h2>
<p>Body</p>
</Card>Conditional rendering
Use plain &&, ternaries, or early returns:
function Status({ ok }: { ok: boolean }) {
return (
<div>
{ok && <p>All good</p>}
{ok ? <span>✓</span> : <span>✗</span>}
</div>
)
}Lists
Map an array. Always include key when items can reorder:
function TodoList({ items }: { items: { id: number; text: string }[] }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.text}</li>
))}
</ul>
)
}The key lets the reconciler match items across renders by identity instead of position. Toggling item 5 in a list of 100 re-renders only that one <li>.
Event handlers
Lowercase (DOM-native): onclick, oninput, onsubmit, onkeydown. Type as the corresponding DOM event:
function Search() {
const [q, setQ] = use('')
return (
<input
value={q}
oninput={(e: Event) => setQ((e.target as HTMLInputElement).value)}
onkeydown={(e: KeyboardEvent) => {
if (e.key === 'Enter') console.log('search', q)
}}
/>
)
}Why lowercase? Flexium passes attributes straight to the DOM — no synthetic event system. That's part of how it stays at 12 KB.
Refs
For imperative DOM access:
import { useRef } from 'flexium/core'
function FocusInput() {
const ref = useRef<HTMLInputElement>(null)
return (
<div>
<input ref={ref} />
<button onclick={() => ref.current?.focus()}>Focus</button>
</div>
)
}ref.current is set after mount and cleared on unmount.
Lifecycle
Use signals + effects for lifecycle. There's no useEffect(fn, []) ceremony:
function ChatRoom({ roomId }: { roomId: string }) {
const [messages] = use<string[]>([])
unsafeEffect(() => {
const socket = connect(roomId)
socket.on('message', m => setMessages(prev => [...prev, m]))
// Cleanup — runs when component unmounts OR when roomId changes
return () => socket.disconnect()
})
return <ul>{messages.map((m, i) => <li key={i}>{m}</li>)}</ul>
}The effect re-runs when its captured signals change (here: roomId since it's a closed-over prop). The returned cleanup runs before the next execution AND on unmount.
Context
For deep prop passing without drilling:
import { Context, use } from 'flexium/core'
const ThemeCtx = Context.create<'light' | 'dark'>('light')
function App() {
return (
<ThemeCtx.Provider value="dark">
<Header />
</ThemeCtx.Provider>
)
}
function Header() {
const theme = use(ThemeCtx)
return <header data-theme={theme}>...</header>
}For app-wide singletons (settings, auth), prefer use(value, { key }) — see Signals.
Component shape stability
Flexium V8-optimizes component instances. Initialize all fields at construction (don't add properties later) for the fastest paths. The framework does this internally; your component code rarely needs to think about it.
Next
→ Rendering — render(), Suspense, ErrorBoundary, Portal.