JSX types
The type system that powers Vincle: Renderable, VNode, and the JSX
namespace.
Every HTML and SVG element is typed per element, so a typo is a compile error:
<dvi clas="card" />//^ error: 'dvi' does not exist// ^ error: 'clas' does not exist on <div>. Did you mean 'class'?The types ship inside @vincle/core, with the attribute names and value unions
you know from React. Nothing from React is bundled or imported at runtime.
Renderable#
Everything a component may return, and everything the renderers know how to render.
import type { RawString, VNode } from "@vincle/core";
export type Renderable = | VNode | RawString | string | number | bigint | boolean | null | undefined | Iterable<Renderable> | AsyncIterable<Renderable> | Promise<Renderable>;| Type | Example | Behaviour |
|---|---|---|
string |
"a & b" |
HTML-escaped and rendered as text |
number / bigint |
42, 100n |
Rendered as text |
boolean / null / undefined |
true, null |
Renders nothing |
RawString |
raw("<b>bold</b>") |
Passes through verbatim, no escaping |
Promise<Renderable> |
Promise.resolve(<p>hi</p>) |
Awaited |
Renderable[] |
[<a/>, <b/>] |
Rendered in order, nesting flattened |
Iterable<Renderable> |
Set, Map.values(), generator |
Drained, then rendered in order |
AsyncIterable<Renderable> |
AsyncGenerator |
Awaited per item, flushed between items |
Anything else, such as a plain object, a Date, or a URL, is rendered through
its string form. A function or a symbol is a type error.
VNode#
One element built by JSX: a tag, its props, its children. It is exported as a
type only: jsx() is the only way to construct one. Use VNode for a
value that holds an element; for anything that merely renders, use the wider
Renderable.
Components#
A component is a plain function, possibly async, returning anything in
Renderable. There is no Component<P> type to apply:
import type { Renderable } from "@vincle/core";
const Greeting = ({ name }: { name: string }) => <h1>Hello, {name}!</h1>;
const AsyncGreeting = async ({ id }: { id: string }) => { const user = await db.users.find(id); return <h1>Hello {user.name}</h1>;};
const Items = () => [<li>a</li>, <li>b</li>];
const Card = ({ children }: { children?: Renderable }) => <div class="card">{children}</div>;To accept children, declare them as Renderable.
Attributes#
Attribute names use React's camelCase spelling; the engine maps each one to its HTML name.
| You write | The document carries |
|---|---|
className |
class |
htmlFor |
for |
tabIndex |
tabindex |
httpEquiv |
http-equiv |
strokeWidth |
stroke-width |
xlinkHref |
xlink:href |
viewBox |
viewBox |
HTML names (class, style, data-*, aria-*, http-equiv, xlink:href…)
can be written directly too.
Values may be a string, a number, a boolean, a raw() string, or
a promise of any of those. A boolean attribute renders as the bare name:
<input disabled /> becomes <input disabled>.
<a href={resolveUrl(slug)} class={["btn", active && "btn-active"]}> link</a>Event handlers#
Handlers are strings, not functions, since Vincle emits HTML. A function throws at render time.
<button onclick="submit()">Click me</button><button onClick="submit()">Both spellings work</button>The
no-unsafe-event-handlers
rule flags inline handlers at the source if you would rather avoid them.
JSX namespace#
Vincle declares its own JSX namespace, re-exported by each JSX runtime entry
point: TypeScript finds it through the jsxImportSource in your
tsconfig.json, so you rarely touch it directly.
| Member | Purpose |
|---|---|
JSX.Element |
What jsx() produces |
JSX.ElementType |
What may appear as a tag: a string, or a component function |
JSX.IntrinsicElements |
Attribute types per element, plus open custom elements |
JSX.IntrinsicAttributes |
Props every element accepts without being attributes (key) |
JSX.ElementChildrenAttribute |
Names the prop JSX children are written into |
Custom elements#
Any name containing a hyphen is a custom element: it accepts any attribute, and
its children are checked as Renderable.
<my-widget theme="dark" data-id="7"> content</my-widget>To type one, augment JSX.IntrinsicElements. The props must be a type
literal, not a named interface (an interface cannot satisfy the
custom-element index signature: error TS2411):
import type { Renderable } from "@vincle/core";
declare module "@vincle/core/jsx-runtime" { namespace JSX { interface IntrinsicElements { "turbo-frame": { src?: string; target?: string; children?: Renderable }; } }}CSSProperties#
Style objects use camelCase properties, converted to kebab-case on output:
import type { CSSProperties } from "@vincle/core";
const styles = { backgroundColor: "#fff", fontSize: "14px", "--custom-var": "value", // CSS variables supported} satisfies CSSProperties;Property names are validated: one carrying :, ; or { is dropped, so keys
coming from data cannot smuggle extra declarations into the attribute. Values are
CSS-escaped for the same reason: a ; in a value stays inside it.
ClassValue#
What the class attribute accepts: a string, or a flat list whose falsy entries
are dropped.
<div class={["card", isActive && "card-active", null]} />// => <div class="card card-active">