Skip to content
Vidact

Learn

Opt-in features

Suspense, transitions, form actions, and other capabilities that you enable per project so unused ones never reach your bundle.

Vidact enables capabilities such as Suspense, transitions, and form actions through compiler feature flags. The table below lists each flag. Consult the compatibility reference for differences from React's behavior.

vite.config.ts
import { vidact } from '@vidact/vite'export default defineConfig({  plugins: [vidact({ features: ['async', 'concurrent'] })],})

Why opt in?

Features use separate runtime entry points so unused capabilities can stay out of the bundle. Calling an opt-in API without its flag produces a compiler diagnostic that names the required feature.

The features

FeatureWhat it enables
asyncSuspense, lazy, and use(promise)
concurrentuseTransition, startTransition, useDeferredValue, flushSync
actionsFunction-valued form action, useActionState, useOptimistic, useFormStatus
retained-uiActivity for hiding UI while keeping its state
css-insertionuseInsertionEffect
unsafe-htmldangerouslySetInnerHTML
profilingProfiler, useDebugValue, captureOwnerStack
frameworkStreaming and static server rendering, document metadata, resource hints, cache, server components and functions. Enabled automatically by Vidact Start.

async

Suspend on data with use(promise) and show a fallback with Suspense. Code-split components with lazy.

tsx
import { lazy, Suspense, use } from 'react'const Chart = lazy(() => import('./Chart.tsx'))function Report({ dataPromise }: { dataPromise: Promise<Report> }) {  const report = use(dataPromise)  return <Chart data={report.series} />}export function Dashboard({ dataPromise }: { dataPromise: Promise<Report> }) {  return (    <Suspense fallback={<p>Loading…</p>}>      <Report dataPromise={dataPromise} />    </Suspense>  )}

Reading a context with use(context) does not need this feature.

concurrent

Mark an update as non-urgent so the UI stays responsive while it is applied:

tsx
import { useDeferredValue, useState, useTransition } from 'react'export function Search({ items }: { items: string[] }) {  const [query, setQuery] = useState('')  const [isPending, startTransition] = useTransition()  const deferredQuery = useDeferredValue(query)  return (    <div>      <input        value={query}        onChange={(event) => startTransition(() => setQuery(event.target.value))}      />      <ul style={{ opacity: isPending ? 0.5 : 1 }}>        {items          .filter((item) => item.includes(deferredQuery))          .map((item) => <li key={item}>{item}</li>)}      </ul>    </div>  )}

Vidact implements transitions with an interruptible scheduler rather than emulating them with timers. Without the feature, all updates are synchronous.

actions

Covered in Forms.

retained-ui

Covered in Conditional rendering.

unsafe-html

dangerouslySetInnerHTML is what it says. It accepts a string or a TrustedHTML value, cannot be combined with children, and is not allowed on void elements, <textarea>, SVG, or MathML. Inserting executable <script> content is rejected. Sanitize on the way in.

profiling

Profiler reports the updater and effect work Vidact performed rather than React's render durations, since there are no renders. captureOwnerStack returns Vidact's owner chain, which is useful in error reporting.

Checking what a feature costs

Because features map to separate runtime entry points (@vidact/runtime/async, @vidact/runtime/concurrent, and so on), a bundle analyzer shows exactly what each one contributed. Remove the flag and the module disappears.