Experience UI Bundle Frontend Generate
MUST activate before editing ANY file under uiBundles/*/src/ (or the bundle's index.html) for visual or UI changes to an EXISTING app — pages, components, sections, layout, styling, colors, fonts, navigation, animations, branding, or any look-and-feel change. Use this skill when modifying pages, components, layout, styling, navigation, or branding in an existing UI bundle app. Activate when the project contains appLayout.tsx, routes.tsx, src/pages/, src/components/, src/styles/global.css, or the bundle's index.html. This skill contains critical project-specific conventions (appLayout.tsx shell, shadcn/ui components, Tailwind CSS, Salesforce base-path routing, module restrictions) that override general knowledge. Without this skill, generated code will use wrong imports, break routing, or ignore project structure. Do NOT use when creating a new app from scratch or the bundle has not been scaffolded yet (use experience-ui-bundle-app-coordinate instead).
- Skill ID
- forcedotcom/sf-skills/experience-ui-bundle-frontend-generate
- Publisher
- forcedotcom
- Repository
- sf-skills
- Installs
- 0
- Files
- 7
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
forcedotcom/sf-skills/experience-ui-bundle-frontend-generateInstalls these files- SKILL.md
- references/component.md
- references/header-footer.md
- references/page.md
- scripts/check-preconditions.sh
- scripts/resolve-ui-bundle.sh
- scripts/verify-rules.sh
What this skill tells the agent
UI Bundle UI
Resolve the Bundle Directory
MUST run scripts/resolve-ui-bundle.sh [project-root] before applying any rule below or writing any file — an ad-hoc find/ls is not a substitute; it does not enforce the exit-code gate below. It reads sfdx-project.json's packageDirectories[0].path (does not assume force-app — the source path is configurable) and looks under <sourceDir>/main/default/uiBundles/:
- Exit 0, bundle path printed to stdout: exactly one bundle directory found — that is the bundle directory. Use that exact directory name; never substitute a different name (e.g. a generic "AcmePortal" example from a prompt template) for the one actually printed.
- Exit 2, candidates printed to stderr: multiple
uiBundles/*subdirectories exist — do not guess, and do not write to any of them, or to a bundle name not in the printed list. Ask the user which app/bundle they mean before editing or running any command. - Exit 1:
sfdx-project.jsonmissing/invalid, or no bundle directory found at all.
Run all npm/lint/build/dev commands from inside the resolved bundle directory, never from the project root.
Preconditions
Before applying any rule below, confirm this is an existing, scaffolded UI bundle: MUST run scripts/check-preconditions.sh <bundle-dir> with the directory resolve-ui-bundle.sh printed.
- Single-bundle (exit 0) case: run it once, on that bundle.
- Multi-bundle (exit 2) case: do not run it yet — first ask the user which app/bundle they mean, per the Resolve step above. Once the user names the bundle, run
check-preconditions.shon that one bundle only. Never run it against multiple candidates speculatively before the user has chosen — that means touching/inspecting bundles the user didn't ask about. - Exit 0: the bundle has
src/appLayout.tsx,src/routes.tsx, andsrc/components/ui/— proceed. - Exit 1, missing pieces listed: this is a fresh SFDX project, a non-UI-bundle React project, or a partially-scaffolded bundle — stop. Do not fall back to generic React knowledge (e.g.
react-router-dom, a hardcoded basename, or raw HTML), and do not hand-writeappLayout.tsx/routes.tsx/a page/a component to "fill in" the missing scaffold, even for a casual, vague, or urgent-sounding request ("just change the header", "make the background blue"). Tell the user the bundle isn't scaffolded yet and direct them toexperience-ui-bundle-app-coordinate(orexperience-ui-bundle-metadata-generate) to scaffold it first. If, after the user names their intended bundle, that one turns out to be unscaffolded, stop and redirect for it — do not silently switch to scaffolding a different candidate instead.
Never invent, assume, or fall back to a bundle name that doesn't appear in the actual <sourceDir>/main/default/uiBundles/ listing on disk — including a name that only appears as example/template text elsewhere (a prompt, a directive, prior conversation). If what's on disk doesn't match that example, disk wins.
Identify the Task
Determine which category the request falls into:
| Category | Examples | Implementation Guide |
|---|---|---|
| Page | New routed page (contacts, dashboard, settings) | references/page.md |
| Header / Footer | Site-wide nav bar, footer, branding, renaming the app | references/header-footer.md |
| Component | Widget, card, table, form, dialog | references/component.md |
A request to rename/rebrand the app (e.g. "call it X everywhere a user would see it") is a Header / Footer task even though it doesn't mention "header" by name — it always touches at least two files: src/appLayout.tsx (header/nav brand text) AND index.html's <title> (browser tab title). Treat these as one atomic change; a rename that only updates one of the two is incomplete.
Pre-built Features (Check Before Hand-Building)
Some capabilities ship as pre-built, tested feature packages. The catalog evolves and is not something you can know from memory — never decide from the request wording alone whether a capability "is" or "isn't" a feature. Before hand-writing any non-trivial capability (anything beyond a plain page, component, or styling change) in this skill:
- Consult the authoritative catalog. Invoke
experience-ui-bundle-features-generate, which runslistto show the current set of installable features. Do not rely on a hardcoded or remembered list — this skill deliberately names none, because any names it listed would go stale. - Detect whether a matching feature is already installed in the bundle — inspect
package.jsondependencies and existingsrc/files. If present, use it as-is; do not reinstall or re-implement. - If a matching feature exists in the catalog but isn't installed, let
experience-ui-bundle-features-generateinstall the tested package. Do not build it from scratch here. - Only hand-build a capability that has no matching catalog feature.
This gate is idempotent: when this skill runs as a phase of experience-ui-bundle-app-coordinate (which installs features earlier in its sequence), step 2 finds the feature already present and this collapses to a no-op. It only does real work when the skill was reached directly — the path that would otherwise skip feature detection.
Layout and Navigation
appLayout.tsx is the source of truth for navigation and layout. Every page shares this shell.
When making any change that affects navigation, header, footer, sidebar, theme, or layout:
- Edit
src/appLayout.tsx— the layout used byroutes.tsx - Replace all default/template nav items and labels with app-specific links and names
- Replace placeholder app name everywhere: header, nav brand, footer,
<title>inindex.html
index.html lives at the bundle root (not under src/), but it is still in scope for this skill whenever branding or the app name changes — the leftover <title>React App</title> / Vite + React boilerplate is a common ship-blocker that npm run lint/npm run build never catches.
Before finishing, confirm: Did I update appLayout.tsx with real nav items and branding? Then run scripts/verify-rules.sh to check for residual boilerplate (see Verification below).
| What | Where |
|---|---|
| Layout, nav, branding | src/appLayout.tsx |
| Document title | index.html (bundle root, outside src/ — still in scope for branding) |
| Root page content | Component at root route in routes.tsx |
React and TypeScript Standards
Routing
Use a single router package. With createBrowserRouter / RouterProvider, all imports must come from react-router (not react-router-dom).
If the app uses a client-side router (React Router, Remix Router, Vue Router, etc.), always derive basename / basepath / base from the document's <base href> tag at runtime. Never hardcode the basename:
const basename = document.querySelector('base')
? new URL(document.querySelector('base').href).pathname.replace(/\/$/, '')
: '/';
const router = createBrowserRouter(routes, { basename });Component Library and Styling
- shadcn/ui for components:
import { Button } from '@/components/ui/button'; - Tailwind CSS utility classes
URL and Path Handling
Apps run behind dynamic base paths. Router navigation (<Link to>, navigate()) uses absolute paths (/x). Non-router attributes (<img src>) use dot-relative (./x). Prefer Vite import for static assets.
TypeScript
- Never use
any— use proper types, generics, orunknownwith type guards - Event handlers:
(event: React.FormEvent<HTMLFormElement>): void - State:
useState<User | null>(null)— always provide the type parameter - No unsafe assertions (
obj as User) — use type guards instead
Module Restrictions
More from forcedotcom
Use this skill in a project
Open any RiverX project, search the Skills panel for forcedotcom/sf-skills/experience-ui-bundle-frontend-generate, and install it in one click.
