Appearance
React & shadcn/ui
The default frontend in the AI-Driven Development Model is built on React with shadcn/ui as the component foundation — a deliberate alternative to the classic SAP Fiori / UI5 stack. When Fiori design compliance is mandated instead, see UI5 Web Components.
Stack
- React + TypeScript + Vite 8 (Rolldown bundler)
- ESM-only; Node ≥ 20.19 / 22.12 (covered by our Node 24 baseline).
build.rolldownOptions(renamed frombuild.rollupOptions); esbuild config block →oxc.- Module Federation is supported natively by Rolldown — no separate plugin required.
- Yarn PnP is not supported — use npm workspaces (our default anyway).
UI
- shadcn/ui (Base UI primitives) + Tailwind CSS v4 — default for modern product-style apps.
components.json→"style": "base-*"(e.g.base-vega); Base UI is the shadcn default since July 2026.- Primitive package:
@base-ui-components/react(replaces the many@radix-ui/react-*packages). - Use the
renderprop — neverasChild(Base UI has noasChild). checkedis strictlyboolean; use theindeterminateparam for partial states.- Select maps differently than Radix (
Popup/Positioner, notContent/Viewport) — not a 1:1 import swap. - Non-interactive
shadcn initin CI must pass-b radixif Radix is intentionally required.
AI-assisted shadcn tooling
This project ships a dedicated shadcn MCP server plus matching agent skills, so the AI agent can:
- Search for relevant components directly against the shadcn registry instead of guessing names or hand-browsing docs.
- Scaffold with the right commands — add components via the correct
shadcnCLI invocation. - Apply best practices out of the box — the skills encode the Base UI conventions above (
renderinstead ofasChild,booleanchecked,Popup/Positionermapping), so generated code follows them without repeated prompting.
See AI-Driven Workflow → Agents & Tooling for how this fits the overall model.
Fiori Building Blocks (annotation-driven)
For SAP-style transactional apps, the model ships a set of React Fiori components (packages/ui-core/components/fiori/) that render CAP's Fiori annotations out of the box — the React/shadcn counterpart to SAP Fiori elements, without UI5:
- Building blocks —
Page(list-report-style list + table),ObjectPage,ScopePage,FioriTable,Field,DataPoint. - Metadata-driven — an
ODataMetadataProvider(basePath="/odata/v4/<domain>/<service>") reads the OData$metadataincl. the@UI.*annotations;useODataMetadataplus generic entity hooks (useEntityData,useInfiniteEntityData) bind any annotated entity with no per-field code. - Value helps —
@Common.ValueListannotations (Wertehilfen) render as searchable pickers automatically. - Fiori-bound features — a feature is wired to its CAP service by placing the
ODataMetadataProviderin the package's Intent; generic hooks then resolve columns, facets, fields, and value helps from the metadata.
This is the annotation-driven productivity of Fiori elements delivered in the modern shadcn stack. It is distinct from UI5 Web Components: you get Fiori building blocks and floorplans (list report, object page) in shadcn styling — not the UI5-based Fiori design itself.
Data Access
- API abstraction: TanStack Query v5 (
@tanstack/react-query) for all OData v4 and REST calls.- There is no React Query v6 — v6 exists only as a Svelte adapter; do not generate v6-style APIs.
odata-queryadditionally for complex filter / paging / expand.- ⚠️ CAP v10:
DecimalandInt64arrive as strings in OData responses ("9.99", not9.99) — parse/type accordingly and avoid strict numeric equality on these fields.
Data Tables
- TanStack Table v8 (stable) + TanStack Virtual for all sortable, filterable, paginated tables.
- TanStack Virtual for row/column virtualization on large datasets (> ~200 rows rendered).
- Pin to v8 — v9 is in beta with a substantially different API (
useTableinstead ofuseReactTable, tree-shakable_features/_rowModelsplugins); do not mix v8 and v9 patterns. - When adopting v9 later: run
npx @tanstack/table intent addfor version-matched agent skills, and useuseLegacyTableonly as a temporary migration aid.
State Management
- TanStack Query — server state.
- Zustand v5 — UI / client state.
- Multi-value selectors must use
useShallowfromzustand/shallow— selectors returning fresh object/array references cause infinite render loops in v5.
- Multi-value selectors must use
i18n
Default Library Choices
Core decisions above are binding; the following are defaults for common concerns. Deviating is fine, but document the reason in the project's own docs.
| Concern | Default | Notes |
|---|---|---|
| Forms | React Hook Form + Zod | shadcn/ui form components are built on RHF; Zod v4 has different imports/APIs than v3 — do not mix |
| Icons | Lucide (lucide-react) | shadcn/ui default icon set |
| Toasts / notifications | Sonner | shadcn/ui recommended toast |
| Charts | Recharts | shadcn/ui chart components are Recharts-based |
| Node-based UIs / flow editors | React Flow (@xyflow/react) | For graph/node editors — not for statistical charts (use Recharts) |
| Drag & drop | dnd-kit | Standard choice; Pragmatic Drag and Drop is the fallback if a blocker arises |
| Positioning / popovers | Floating UI (@floating-ui/react) | Base UI's positioning engine; use directly only for custom positioned components — Base UI components handle it internally |
| Code editor | CodeMirror 6 | Lightweight, modular, tree-shakable; Monaco Editor only for heavy needs (full IDE features, TS language service) and always lazy-loaded |
| Animations | Motion (motion package) | Formerly Framer Motion — import from motion/react, not framer-motion |
| Dates | date-fns | Tree-shakable; wire locales to the i18next language |
| Routing | React Router v7 — library mode | Framework mode conflicts with our Vite + Module Federation setup; evaluate TanStack Router for new standalone apps |
| Offline cache persistence | @tanstack/react-query-persist-client + @tanstack/query-async-storage-persister | Persist the TanStack Query cache for offline use via PersistQueryClientProvider. Back the async persister with IndexedDB through idb-keyval (get/set/del satisfy the AsyncStorage interface — avoid localStorage for larger caches). Tune gcTime/maxAge (e.g. 24 h); resume offline writes with queryClient.resumePausedMutations() |
| PWA / service worker | vite-plugin-pwa (Workbox) | Web App Manifest injection, service worker with offline precache + runtime caching, auto-update (stale-while-revalidate). Pairs with the query persistence above for a fully offline-capable app |