forcedotcom/sf-skills7 files

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).

Specification
Skill ID
forcedotcom/sf-skills/experience-ui-bundle-frontend-generate
Publisher
forcedotcom
Repository
sf-skills
Installs
0
Files
7
Synced
Sep 23, 2026
How to use it

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.json missing/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.sh on 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, and src/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-write appLayout.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 to experience-ui-bundle-app-coordinate (or experience-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:

CategoryExamplesImplementation Guide
PageNew routed page (contacts, dashboard, settings)references/page.md
Header / FooterSite-wide nav bar, footer, branding, renaming the appreferences/header-footer.md
ComponentWidget, card, table, form, dialogreferences/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:

  1. Consult the authoritative catalog. Invoke experience-ui-bundle-features-generate, which runs list to 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.
  2. Detect whether a matching feature is already installed in the bundle — inspect package.json dependencies and existing src/ files. If present, use it as-is; do not reinstall or re-implement.
  3. If a matching feature exists in the catalog but isn't installed, let experience-ui-bundle-features-generate install the tested package. Do not build it from scratch here.
  4. 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:

  1. Edit src/appLayout.tsx — the layout used by routes.tsx
  2. Replace all default/template nav items and labels with app-specific links and names
  3. Replace placeholder app name everywhere: header, nav brand, footer, <title> in index.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).

WhatWhere
Layout, nav, brandingsrc/appLayout.tsx
Document titleindex.html (bundle root, outside src/ — still in scope for branding)
Root page contentComponent 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, or unknown with 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.

Use this skill