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.
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
| Feature | What it enables |
|---|---|
async | Suspense, lazy, and use(promise) |
concurrent | useTransition, startTransition, useDeferredValue, flushSync |
actions | Function-valued form action, useActionState, useOptimistic, useFormStatus |
retained-ui | Activity for hiding UI while keeping its state |
css-insertion | useInsertionEffect |
unsafe-html | dangerouslySetInnerHTML |
profiling | Profiler, useDebugValue, captureOwnerStack |
framework | Streaming 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.
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:
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.