Skip to content

UI

The client is an Angular app (standalone components, signals) whose shell, login, profile, organization and admin pages come from core. Your product adds its own pages inside that shell. Use this page when you add your first screen or change the look.

The shell and your routes

apps/<product>/client/src/app/app.config.ts wires core and your product together:

provideRouter(coreRoutes({ product: productConfig, routes: keelmintRoutes })),
{ provide: PRODUCT_NAV, useValue: keelmintNav },
  • coreRoutes({ product, routes, adminRoutes }) gives you core’s auth, profile, organization, admin, notifications and health pages plus the dashboard layout; your routes render inside the layout (core puts the organization guard in front of each).
  • PRODUCT_NAV is your sidebar: { labelKey, path, permission?, units?, icon?, section? }. The first main item without a permission is the home page, where login lands (a platform admin lands on /users). A platform admin opens every product page with no active organization: its lists span every organization (Your product). An item’s label also titles the header of its pages. permission must be held organization-wide, unless units: true also accepts it in a unit (pair it with unitPermissionGuard on the route). section: 'admin' puts it under the Administration heading instead: shown to whoever holds its permission (platform admins only without one). It only places the link - a page for organization or unit admins goes in routes behind permissionGuard/unitPermissionGuard; adminRoutes live under /admin and are platform-admin-only.

Add a page:

app.routes.ts
export const productRoutes: Route[] = [
{
path: 'projects',
canActivate: [authGuard, permissionGuard('Projects.Read')],
loadComponent: () => import('@acme/projects-feature').then((m) => m.ProjectsPage),
},
];
export const productNav: ProductNavItem[] = [{ labelKey: 'nav.projects', path: '/projects', icon: 'lucideFolder', permission: 'Projects.Read' }];

Icons come from lucide through @ng-icons/lucide; register the ones your nav uses in nav-icons.ts. Put the page itself in a feature library (libs/<product>/client/<name>-feature), and its API calls in a data-access library, so the import rules in the Project tour hold.

Components

  • spartan/ui (Helm components over Brain primitives, the shadcn look for Angular) in libs/core/client/spartan-ui/*, imported as @spartan-ng/helm/<component>: alert, badge, button, card, dialog, dropdown menu, field, input, label, popover, progress, select, separator, sonner (toasts), switch, table, textarea, tooltip. Add another with pnpm nx g @spartan-ng/cli:ui --name=<component>.
  • Lists: lib-data-table with injectListState (libs/core/client/common/ui-table) keeps search, sort, page and column filters in the URL and pairs with the API’s shared list contract. Keelmint’s todo pages are the example.
  • Forms: Angular signal forms with lib-form-field (@core/ui-form-field) for labels and validation messages.
  • Permissions in templates: *libHasPermission="'Projects.Create'" (see Permissions and roles).

Theme and branding

  • The design system (libs/core/client/design-system/src/styles) defines the palette, semantic tokens for light and dark, fonts and typography. Light, dark and system themes switch from the header; the choice is remembered.
  • Brand your product by overriding tokens in its client’s src/styles.css (after the design-system import), for example the --color-brand-* scale. Core stays untouched.
  • Fonts load from Google Fonts by default (fonts.css); self-host them if you need to.
  • The product name in the header, page titles and emails is name in product.config.ts.