Fig, a TS UI Runtime Based on React

nebrelbug1 pts0 comments

Introducing Fig - Ben Gubler<br>Ben Gubler<br>@bgub

ARCSENEORUSK<br>© Ben GublerLightDarkSystem

ARCSENEORUSK<br>© Ben GublerLightDarkSystem

8/14/2026·9 min read<br>Introducing Fig<br>A small TypeScript UI runtime based on React Fiber.<br>#frontend#open-source<br>In this entry<br>§ Quick Intro<br>§ Fig's Philosophy<br>§ Components<br>§ Hooks and Transitions<br>§ Event Handlers<br>§ DOM Binding<br>§ Suspense<br>§ Context<br>§ Data Resources<br>§ Server Components (Without a Metaframework)<br>§ Server Components (With a Metaframework)<br>§ Asset Resources<br>§ View Transitions<br>§ Other<br>§ Try It

Raw (for LLMs)<br>View MarkdownCopy Raw

Quick Intro<br>Fig is a TypeScript UI runtime based on React. Quick pitch:<br>Roughly 50% the bundle size of React<br>Full fiber/concurrent rendering support<br>Built-in keyed data resources with Suspense, streaming SSR/hydration, stale-while-refresh<br>Server components, but easy-to-understandInspired by TanStack, we treat server components as just data that can be streamed and rendered. When you refresh the key, the boundary re-renders. You can even change the wire format.

Effects, events, DOM bindings, transitions, actions, and data loaders all take AbortSignal<br>(Slightly) faster rendering performance<br>Adapters for TanStack Router and TanStack Start<br>Nice features meant for framework creators (easy ways to granularly declare component dependencies on CSS or other assets)<br>My personal website (https://bengubler.com) is built using Fig TanStack Start. Feel free to take a look at the source or ask your agent what it thinks !<br>There are some minor syntax differences from React, but migrating your site (if you use a Vite-based framework) should be trivial thanks to LLMs!<br>Fig's Philosophy<br>It's become a bit fashionable to hate on React, but that's because it clearly won . React is a beautiful and elegant way to express view as a function of state. Fiber/concurrent rendering are brilliant (article coming soon). Server components can help eliminate network waterfalls and trim down client bundle size. Performance, contra what you see online, is easily good enough for any practical use case if you write good code. Signal-based frameworks sound really nice in theory, but in practice often have complicated tradeoffs around things like SSR, server components, and dev HMR.<br>With the passage of time, frameworks become slightly larger, get locked into old decisions, and have to maintain backwards compatibility. Fig is an attempt to reimplement the beautiful core ideas of React in a slightly smaller package with some different API decisions. In general I try to steer Fig towards using platform semantics (AbortSignals instead of React's cleanup functions) and a high-level API that's easy to use without a metaframework.<br>In most cases, API methods with the same name as React will also share the same signature! When they're meaningfully different, Fig usually uses a different name.<br>Components<br>import { createMixin } from "@bgub/fig";

const externalLink = createMixin((_context, label: string) => ({<br>target: "_blank",<br>rel: "noopener noreferrer",<br>"aria-label": `${label} (opens in a new tab)`,<br>}));

function Greeting() {<br>return (<br><><br>Hello from Fig!

href="https://github.com/bgub/fig"<br>mix={externalLink("View the source")}<br>View the source

);

In Fig, there are only function components , no class components. If you need to catch an error, Fig has a built-in ErrorBoundary you can use or wrap rather than defining your own using a class component.<br>Inspired by Remix V3, we also support mixins that let you create reusable utilities for props! The example above is a bit contrived but mixins are really useful for accessibility (especially when you pair them with event handlers, see further down the page).<br>One other important thing to note: Fig uses native attribute names , like class instead of className and autocomplete instead of autoComplete. This lets us share the same types with native HTML.<br>Oh, also we renamed dangerouslySetInnerHTML to unsafeHTML.<br>Hooks and Transitions<br>function SearchResults({ query }: { query: string }) {<br>const [results, setResults] = useStatestring[]>([]);<br>const [isPending, startSearchTransition] = useTransition();

useReactive(() => {<br>startSearchTransition(async (signal) => {<br>const url = `/api/search?q=${encodeURIComponent(query)}`<br>const response = await fetch(url, { signal });<br>setResults(await response.json());<br>});<br>}, [query]);

return {isPending ? "Searching…" : results.join(", ")};

useState is just the same as React. useReactive is Fig's version of useEffect; just like in React, it runs after a component mounts and then after every committed render when its (optional) dependencies have changed. A transition groups state updates within its scope into lower-priority work that can be pre-empted.<br>Unlike React effects, Fig effects take an AbortSignal instead of returning a cleanup function. This makes auto-cancelling superseded network calls easy!<br>We renamed lifecycle hooks from their React equivalents 1) for clarity and 2) so neither you nor agents get mixed up....

react components server based view function

Related Articles