Screens
Connect host-owned routes to TailorKit screens with typed context.
Screen matching is how a host app tells TailorKit which screen is active.
TailorKit consumes mounted ScreenMatch components; it does not own routing,
read window.location, or mutate browser history.
Declare A Match
Place tailor.ScreenMatch inside the host route or layout that owns the active
data. screen is the stable TailorKit surface key, and context is the data
for that surface.
<tailor.ScreenMatch screen="/users/detail" context={{ user }}>
{children}
</tailor.ScreenMatch>Typed Context
screen is typed from schema.screens, and context is typed from the
selected screen's context schema.
const schema = defineSchema({
components: {},
screens: {
"/users/detail": screen({
context: z.object({
user: z.object({
id: z.string(),
name: z.string(),
}),
}),
}),
},
});This match type-checks:
<tailor.ScreenMatch
screen="/users/detail"
context={{
user,
}}
/>Invalid screen names or missing context fields fail at compile time.
Loading Without Stale Context
Use isLoading when the route is mounted but its data is not ready. Loading
matches can omit context.
<tailor.ScreenMatch
screen="/users/detail"
isLoading={isLoading}
context={isLoading ? undefined : { user }}
>
{children}
</tailor.ScreenMatch>The API is intentionally strict:
isLoading: trueallowscontextto be omitted.isLoading?: falserequires the full typedcontext.
This prevents a previous route's context from being reused while the next route is still loading.
Nested Matches
When multiple matches are mounted, the deepest mounted match is preferred. TailorKit passes the mounted chain to each app. If an app does not implement the deepest screen, the worker walks back through parent matches until it finds a screen the app implements.
<tailor.ScreenMatch screen="/projects" context={projectContext}>
<ProjectLayout>
<tailor.ScreenMatch screen="/projects/issues/detail" context={issueContext}>
<IssuePage />
</tailor.ScreenMatch>
</ProjectLayout>
</tailor.ScreenMatch>When the nested issue route unmounts, TailorKit uses the parent project match.
If both matches are mounted but an app only implements /projects, it receives
the parent match and its parent context.
Sibling matches at the same depth are ambiguous. TailorKit warns in development and selects one by mount order so production keeps rendering.
Render The Current Match
tailor.AppView renders the selected match into a TailorKit app.
const { apps } = tailor.useApps();
return (
<tailor.ScreenMatch screen="/home" context={context}>
{apps.map((app) => (
<tailor.AppView key={app.id} app={app} />
))}
</tailor.ScreenMatch>
);Multiple apps can render the same current match.
You can also pass a screen and context directly when the app view is not
rendered under the route that owns the active ScreenMatch.
<tailor.AppView app={app} screen="/home" context={context} />Registry State
The same tailor instance caches the app registry. Use useApps() in React and
getApps() or getApp(id) when you need to read the cached values outside a
render path.
const { apps, error, status } = tailor.useApps();
const todoApp = tailor.getApp("todo");App URL Resolution
tailor.AppView resolves deployed app clients from the TailorKit metadata
assetsBaseUrl and the app registry item's current deployment.
new URL(
`projects/${app.projectId}/apps/${app.id}/deployments/${app.currentDeployment.id}/files/client.js`,
assetsBaseUrl,
);Local apps can still provide a client path directly:
<tailor.AppView
app={{
id: "todo",
clientPath: "/tailorkit/todo/client.js",
}}
/>Responsibilities
The host app owns:
- routing.
- loading state.
- data fetching.
- choosing the active TailorKit screen.
TailorKit owns:
- fetching and caching the app registry.
- tracking mounted
ScreenMatchentries. - selecting the deepest active match chain.
- rendering the first implemented match into each TailorKit app.