Private beta. We're working directly with early teams. Talk to a founder
TailorKit

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: true allows context to be omitted.
  • isLoading?: false requires the full typed context.

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 ScreenMatch entries.
  • selecting the deepest active match chain.
  • rendering the first implemented match into each TailorKit app.