Integrating Into Your App
End-to-end host wiring for Next.js or TanStack apps—new or existing—with f-ui list pages, i18n, LinkProvider, and stock shadcn primitives.
Use this guide when you are adding f-ui to a host app (greenfield or an existing shadcn + React project) and want list pages, forms, and detail views to look and behave consistently.
If you only need a single open-registry item (Date Picker, Multi-Select, etc.), start with Installation instead.
When To Use This Guide
- You already use shadcn/ui (or just ran
shadcn init) and want a QueryList browse page (filter bar + table + pagination). - You use Next.js App Router or TanStack Start / TanStack Router with React 19.
- You need Plus registry items (Table, Query List, Form Page, Descriptions, …).
- You need Formily validators to track locale switches, or clipped focus rings in the filter bar — this guide covers those host wiring fixes.
Quick Checklist
- Confirm Tailwind + React + shadcn (
components.json,@/components/ui) — runshadcn initonly if the host is not set up yet. - Add
@f-uiand@f-ui-plustocomponents.json(Plus needsFUI_PLUS_REGISTRY_TOKEN). - Install
@f-ui-plus/query-list(pulls Formily, Query Filter, Table, Data List Chrome, Page). - Mount
<I18nProvider locale={…}>at the app root. - SPA hosts: mount
<LinkProvider linkComponent={YourRouterLink}>next toI18nProvider(same shell). Without it,backHref/ tablehref/ Copyable are native<a>and reload the document. - Add host
src/lib/app-locale.tsso Formily validators track locale switches. - Leave
components/ui/*as stock shadcn — do not hand-edit primitives. - Add
--fui-control-h*CSS tokens to your global stylesheet (f-ui-owned sizing). - Wire your first list route — see CRUD Page Patterns.
1. Prepare the Host App
f-ui assumes a standard shadcn layout: Tailwind v4 (or v3 with CSS variables), @/components/ui, @/lib/utils.
Existing app: skip scaffolding if those pieces are already in place; jump to registries (§2).
New app: scaffold, then initialize shadcn:
Create an App Router project, then initialize shadcn:
npx create-next-app@latest my-app
cd my-app
npx shadcn@latest initFollow the TanStack Start scaffold, then:
pnpm dlx shadcn@latest initSet "rsc": false in components.json if you are not using React Server Components for UI files.
Any Vite + React app works as long as path aliases match components.json (@/components, @/lib, …).
2. Configure Registries
Copy this components.json registries block (adjust tailwind.css path to your project):
{
"registries": {
"@f-ui": "https://ui.isaacfei.com/r/{name}.json",
"@f-ui-plus": {
"url": "https://ui.isaacfei.com/api/plus/r/{name}.json",
"headers": {
"Authorization": "Bearer ${FUI_PLUS_REGISTRY_TOKEN}"
}
}
}
}Plus token
- Create a token on the tokens page.
- Export
FUI_PLUS_REGISTRY_TOKENin.env.localor your shell. - Verify:
curl -H "Authorization: Bearer $FUI_PLUS_REGISTRY_TOKEN" \
https://ui.isaacfei.com/api/plus/r/query-list.jsonSee Installation — Plus Registry for CI/CD notes.
3. Install the List-Page Stack
Install query-list once; the CLI resolves Plus dependencies (Formily, Query Filter, Table, Data List Chrome, Page, and open-registry peers such as Select and Date Range Picker):
FUI_PLUS_REGISTRY_TOKEN=xxx pnpm dlx shadcn@latest add @f-ui-plus/query-listFUI_PLUS_REGISTRY_TOKEN=xxx npx shadcn@latest add @f-ui-plus/query-listOptional add-ons for a full CRUD loop:
| Goal | Registry item |
|---|---|
| Read-only detail grid | @f-ui-plus/descriptions |
| Create / edit full page | @f-ui-plus/form-page |
| Card-style rows instead of table | QueryList view="list" (same @f-ui-plus/query-list install). Object Page regions use Related List + List — see List Surfaces |
Component reference: Query List, Form Page.
4. Wire Internationalization
f-ui strings are separate from your app i18n. Mount the provider once around routes that render f-ui:
import { I18nProvider } from "@/components/f-ui/i18n/i18n-provider";
export function AppProviders({
locale,
children,
}: {
locale: string;
children: React.ReactNode;
}) {
return <I18nProvider locale={locale}>{children}</I18nProvider>;
}localemust update on language switch (re-render the provider), not a one-offgetLocale()read. See Internationalization.- Optional
tprop bridges your message catalogs; built-in English and Chinese bundles cover defaults.
Host app-locale adapter (required for Formily)
Vendored Formily imports @/lib/app-locale. Your app must provide this
module so validator messages follow the active locale.
Contract: useAppLocale() returns the current locale string and
subscribes so Form trees re-render when language changes without a full
reload. A one-off getLocale() in a parent that never re-renders is not enough.
Paraglide (Next.js or TanStack)
Paraglide is not TanStack-only. On Next.js App Router (or TanStack Start / Router), use the same cookie + client notify pattern this docs site uses:
// src/lib/app-locale.ts
"use client";
import { useSyncExternalStore } from "react";
import { getLocale } from "@/paraglide/runtime";
const listeners = new Set<() => void>();
export function notifyAppLocaleChanged() {
listeners.forEach((listener) => listener());
}
function subscribe(onStoreChange: () => void) {
listeners.add(onStoreChange);
return () => {
listeners.delete(onStoreChange);
};
}
export function useAppLocale(): string {
return useSyncExternalStore(subscribe, getLocale, getLocale);
}After setLocale(next, { reload: false }), call notifyAppLocaleChanged() so
useAppLocale() / I18nProvider see the new value. Wire that next to your
language switcher (Language Selector, navbar, etc.).
TanStack hosts that already re-render on navigation can additionally subscribe
with useRouterState() (or equivalent) if locale is also encoded in the URL —
the Paraglide notify path above is still required for reload: false cookie
switches.
Next.js without Paraglide (locale segment)
If you drive locale only from the App Router segment (no Paraglide):
// src/lib/app-locale.ts
"use client";
import { useParams } from "next/navigation";
export function useAppLocale(): string {
const params = useParams();
return typeof params.locale === "string" ? params.locale : "en";
}If this file is missing, shadcn add succeeds but Formily validation stays in the wrong language after switching locales.
Routing / Link
Install the open-registry link item (@f-ui/link) so Page back links, link cells, Copyable, and row href actions use your client-side router.
Resolution order: prop linkComponent → LinkProvider → native "a".
SPA hosts must mount LinkProvider
A missing provider is a native document link, not a no-op. Clicking Page Back, a table href, or Copyable then reloads the whole app (sidebar, session /me, list skeletons). That is the most common “list → detail → Back flashes the shell” bug.
MPA / static sites may omit the provider on purpose. SPA hosts (Next App Router, TanStack Router / Start) must mount it next to I18nProvider. In development, f-ui logs a one-time console warning when it falls back to "a" without a provider or a local linkComponent="a".
What Goes Wrong
| Host setup | What the operator sees |
|---|---|
LinkProvider + router Link | Client navigation. Sidebar stays. List cache / keep-mounted layouts survive. |
| No provider (SPA) | Browser follows <a href>. Full document load. Session, KB switcher, and lists remount. |
Provider mounted but adapter ignores href | Same as native <a> if the adapter does not intercept the click. |
Pass linkComponent="a" (or a local native adapter) on a single surface when a real document link is intentional — that silences the DEV warning for that tree.
Next.js App Router
The adapter and provider must live in a Client Component boundary (file starts with "use client"):
"use client";
import { forwardRef } from "react";
import Link from "next/link";
import { LinkProvider } from "@/components/f-ui/link/link-provider";
import type { LinkComponentProps } from "@/components/f-ui/link/link-types";
const RouterLink = forwardRef<HTMLAnchorElement, LinkComponentProps>(
function RouterLink(props, ref) {
return <Link {...props} ref={ref} />;
},
);
export function AppProviders({ children }: { children: React.ReactNode }) {
return <LinkProvider linkComponent={RouterLink}>{children}</LinkProvider>;
}The host wrapper owns Next prefetch defaults. Dense tables may disable viewport prefetch to avoid flooding the network with row/link prefetch work.
TanStack Router
f-ui only emits absolute or app-relative href strings. TanStack’s typed in-app destination is to. The host adapter must put that string on the channel your router version actually intercepts.
If Back still reloads the document, the adapter is painting href on a native attribute only. Map href → to (typical), or spread the untyped href channel if that version treats it as a destination:
import { forwardRef } from "react";
import { Link } from "@tanstack/react-router";
import type { LinkComponentProps } from "@/components/f-ui/link/link-types";
const RouterLink = forwardRef<HTMLAnchorElement, LinkComponentProps>(
function RouterLink({ href, children, ...props }, ref) {
return (
<Link ref={ref} to={href} {...props}>
{children}
</Link>
);
},
);Verify in DevTools: the Back control is a TanStack Link (data-status) and a left-click does not trigger a document navigation. Middle-click / open in new tab must still work (one <a>, forwarded ref).
FormPage guard composition
When unsaved-change interception must cover generated back / row / cell links, mount the router-specific guard Link inside the guard provider:
import {
FormNavigationGuardProvider,
GuardedLink,
} from "@/components/f-ui/form-page/navigation-guard/next-app-router";
import { LinkProvider } from "@/components/f-ui/link/link-provider";
<FormNavigationGuardProvider>
<LinkProvider linkComponent={GuardedLink}>{children}</LinkProvider>
</FormNavigationGuardProvider>;GuardedLink (Next App Router adapter) forwards the anchor ref and props while intercepting onNavigate when the form is dirty.
5. Respect Host Shadcn Primitives
Do not hand-edit `components/ui/*`
Treat everything under src/components/ui/ as vendored shadcn. Keep
the files upstream-verbatim from shadcn init / shadcn add. Do not
patch class strings, props, or behavior by hand to match an older f-ui dense
contract.
f-ui recipes compose on top of your host primitives (Input, Button,
Dialog, …). Visual or behavioral changes belong in:
- f-ui / app composition — wrappers,
classNameat the call site, CSS that targetsdata-slotwhere the primitive exposes it - CSS tokens (§6) — f-ui-owned control heights and gaps
- Fresh registry pulls —
pnpm exec shadcn add <component> --overwritewhen you intentionally refresh a primitive
Do not copy src/components/ui/ from this docs repo into your app, and do
not “restore” local forks after shadcn add. If something looks off after a
pull, reinstall the current @f-ui / @f-ui-plus item instead of forking the
primitive.
6. CSS Design Tokens
Add f-ui control height tokens to your global CSS (e.g. src/app/globals.css or src/styles.css):
:root {
--fui-control-h: 2rem;
--fui-control-h-sm: 1.75rem;
--fui-control-h-lg: 2.25rem;
--fui-form-action-gap: 0.5rem;
}Date pickers, multi-select anchors, and data-list toolbar buttons read these
variables for f-ui-owned shells. They do not require editing host
components/ui/*. Without the tokens, those f-ui surfaces fall back to
hard-coded Tailwind classes.
7. First List Page (Minimal Wiring)
- Define a data-list schema (
defineDataListSchema) — columns and filter fields share one schema. See Table and /showcases/orders. - Hold list params in React state —
useListState()(from data-list internals) gives{ params, onParamsChange }. URL sync is opt-in; default keeps list URLs clean. - Implement a
DataListAdapter— connects TanStack Query (or your fetch layer) to pagination, sort, and filter params emitted by Query List. - Render
QueryListinsidePageContainer:
import { PageContainer } from "@/components/f-ui/page/page-container";
import { QueryList } from "@/components/f-ui/query-list/query-list";
import { Statistic } from "@/components/f-ui/statistic/statistic";
import { StatisticGroup } from "@/components/f-ui/statistic/statistic-group";
import { useListState } from "@/components/f-ui/data-list-internals/hooks/use-list-state";
import { defineDataListSchema } from "@/components/f-ui/data-list-internals/schema/define-data-list-schema";
const ordersSchema = defineDataListSchema({
/* columns + filters */
});
export function OrdersPage() {
const { params, onParamsChange } = useListState();
const adapter = useOrdersAdapter({ params }); // your TanStack Query hook → DataListAdapter
// summary = useQuery({ queryKey: ["orders-summary", params], … })
return (
<PageContainer title="Orders">
<QueryList
listCode="orders"
schema={ordersSchema}
adapter={adapter}
params={params}
onParamsChange={onParamsChange}
getRowId={(row) => row.id}
stats={
<StatisticGroup>
<Statistic variant="card" title="Orders" value={summary?.orderCount ?? null} />
</StatisticGroup>
}
/>
</PageContainer>
);
}- Invalidate after mutations — use a stable
listCodeand query-key prefix so create/update/delete refreshes the table.
Full recipe (toolbar, batch actions, footer inset): Query List. Architecture: CRUD Page Patterns.
8. Keep Primitives Upstream (Recommended)
When you need a newer shadcn primitive, refresh it with the CLI — never by hand-editing the generated file:
pnpm exec shadcn add button --overwriteHost apps do not need to mirror this docs repo’s internal
verify:primitives checks on input / button / textarea. Those guards are
for this repository’s own CI. In your app:
- Keep
components/ui/*as shadcn delivers them - Reinstall current
@f-ui/@f-ui-plusitems if a recipe looks wrong after a primitive refresh - Prefer fixing clipped rings / layout bugs by updating Query Filter (or the
owning f-ui item), not by forking
ui/*
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Focus ring clipped in filter bar | Stale Query Filter wrapper | Reinstall current @f-ui-plus/query-filter |
Plus add returns 401 | Missing / expired token | Set FUI_PLUS_REGISTRY_TOKEN; curl-verify (§2) |
| f-ui labels English after switching to 中文 | I18nProvider locale prop stale | Re-render provider on locale change (i18n) |
| Formily validation language wrong | Missing @/lib/app-locale | Add host adapter (§4) |
Tempted to patch ui/button after add | Local fork habit | Leave stock; compose outside components/ui/ (§5) |
Next Steps
- Installation — single-item installs, namespace URLs, registry index.
- Internationalization — host translator
t, per-component hooks. - CRUD Page Patterns — list, detail, Form Page decision tree.
- Table · Form Page — props and examples.