> ## Documentation Index
> Fetch the complete documentation index at: https://tailorkit.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Match app UI to page context

> Choose UI that fits the current page and its context.

A view is a version of the UI in a [slot](/docs/concepts/slots), matched to a context. The contract names those contexts and describes the data available in each one.

A global side panel might show an overview on a members list, then details about one member when you open their page. The panel stays in place; its view changes.

## Define the contexts

Use paths to describe a hierarchy, from general to specific:

```ts title="patchsmith.ts" theme={null}
import { defineContract } from "patchsmith";
import { z } from "zod";

export const contract = defineContract({
  views: {
    "/": z.object({}),
    "/members": z.object({}),
    "/members/detail": z.object({
      member: z.object({ id: z.string(), name: z.string() }),
    }),
  },
  slots: {
    panel: { views: ["/", "/members", "/members/detail"] },
  },
});
```

`/` is a default context. These names don't need to match your browser URLs. Each slot lists the contexts it supports and can have a different view for each.

## Register the current context

Call `useViewContext` inside your Patchsmith provider. Register general contexts in layouts and specific contexts in pages:

<CodeGroup>
  ```tsx title="Root layout" theme={null}
  import type { ReactNode } from "react";
  import { patchsmith } from "@/lib/patchsmith-client";

  export function RootLayout({ children }: { children: ReactNode }) {
    patchsmith.useViewContext("/", { context: {} });
    return children;
  }
  ```

  ```tsx title="Members layout" theme={null}
  import type { ReactNode } from "react";
  import { patchsmith } from "@/lib/patchsmith-client";

  export function MembersLayout({ children }: { children: ReactNode }) {
    patchsmith.useViewContext("/members", { context: {} });
    return children;
  }
  ```

  ```tsx title="Member page" theme={null}
  import { patchsmith } from "@/lib/patchsmith-client";

  export function MemberPage({
    member,
  }: {
    member: { id: string; name: string };
  }) {
    patchsmith.useViewContext("/members/detail", { context: { member } });
    return <h1>{member.name}</h1>;
  }
  ```
</CodeGroup>

The hook registers the context when mounted, validates its data, and updates it when values change. On unmount, it removes that registration. Register each declared ancestor too, as the layouts above do.

## Select the closest matching view

Patchsmith uses the deepest mounted context. For each slot, it checks that path, then its parents, selecting the first supported view supplied by the app:

```text theme={null}
/members/detail → /members → /
```

On the members list, the panel can show an overview. Opening a member registers `/members/detail`, so the panel switches to that member's view. Leaving the page unregisters it and returns to the overview. The same works for a global navbar.

If no matching view is available, nothing renders for that app in the slot. To write those views, see [Write app UI](/docs/writing-apps/ui).

## SDK reference

`defineContract({ views })` maps paths starting with `/` to object schemas. Context fields from declared ancestors combine with the selected view’s fields. Child schemas cannot repeat ancestor field names. A fallback receives its own context and its ancestors’ context, excluding fields from deeper paths.

Call `patchsmith.useViewContext(path, state)` on the client returned by `createClient`:

| Argument | Meaning |
| - | - |
| `path` | A declared context path. The deepest mounted path is active. |
| `state.context` | Required. Data for that path's schema, or `undefined` while unavailable. |
| `state.loading` | Defaults to `false`. Loading contexts do not publish data. |
| `state.error` | An `Error`, any string, or `true` marks a failure. Errors take precedence over loading. `false`, `null`, and `undefined` mean no error. |

```ts theme={null}
export interface ViewContextState<TContext = unknown> {
  context: TContext | undefined;
  loading?: boolean;
  error?: boolean | string | Error | null;
}
```

While member data loads, register the context with `loading: true`:

```tsx title="While member data loads" theme={null}
patchsmith.useViewContext("/members/detail", {
  context: undefined,
  loading: true,
});
```

## Frequently asked questions

Register context from your pages and layouts. Slots and apps can each support a subset of contexts.

<AccordionGroup>
  <Accordion title="Does Patchsmith watch my router automatically?">
    Call `useViewContext` in each relevant page or layout. Its mount and unmount
    lifecycle supplies the active context, so it works with different routers.
  </Accordion>

  <Accordion title="Does every slot need a view for every context?">
    No. A slot can support a subset, and an app can provide a subset of those
    views. Matching falls back through parent paths. Without a match, that app's
    UI is hidden.
  </Accordion>
</AccordionGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.