Developer Documentation

Design System & UI Components

The Veltrix portal and community apps render from one shared component kit, shipped as the @veltrixsecops/app-sdk/ui subpath of the SDK. App authors get the same buttons, tables, and form controls the portal uses, themed automatically to each tenant's brand and light/dark preference — because theming is driven by design tokens the host owns.

Shipped & installable today

The kit is live in @veltrixsecops/app-sdk (v3.5.x) at the /ui subpath — 24 components and 2 hooks you can import today. At packaging time the CLI compiles your client/index.tsx into a hermetic ESM bundle where react, react-dom, and every @veltrixsecops/app-sdk import (including /ui) are resolved from the host at runtime via globalThis.__VELTRIX_APP_RUNTIME__.

The declarative App UI & Navigation contract — an app's client.pages[], the platform-owned chrome (breadcrumb, app header, navigation, permission gating, loading + error states), page layouts, and the token-driven theme — is documented in App UI & Navigation.

Architecture

A component kit that apps import has to be publicly installable, so it ships as the @veltrixsecops/app-sdk/ui subpath of the public SDK. But rather than bundle a second copy of the components, the kit copies the platform's prop types and delegates at render time to the host portal's own components (via globalThis.__VELTRIX_APP_RUNTIME__.ui). One implementation, no drift: app authors see exactly what the portal renders, and outside the platform (local dev, tests) each component falls back to a minimal, unstyled, accessible version so nothing crashes.

Import Contains React?
@veltrixsecops/app-sdk Handler contracts, manifest types, ctx.platform, layout helpers No (server-safe)
@veltrixsecops/app-sdk/ui The shared component kit (24 components + useToast/useConfirmDialog), delegating to the host at runtime Yes
@veltrixsecops/app-sdk/hooks useAppContext, usePipelineStatus, useAppBranding, usePermissions Yes
@veltrixsecops/cli init / validate / package / login / sandbox / dev / deploy / config / drift No

Predictable shell, flexible body. The platform renders the chrome for every app page — breadcrumb, app header, navigation, permission gating, loading skeletons, and error boundary — identically across every vendor's app. Your component renders only the page body and composes it from this library. Apps get full freedom inside the frame but cannot alter the chrome or the brand palette, so the portal never becomes a patchwork of vendor themes. (The declarative side of this — client.pages[] — is documented in App UI & Navigation.)

Tokens are CSS custom properties owned by the host. The portal sets the semantic scales — --vx-color-primary, surface / border / content, and their dark variants — on :root; components only ever read var(--vx-*). The consequence is powerful: an app's UI automatically follows the tenant's light/dark theme (and future tenant branding) with zero work from the app author. Apps may define their own app-scoped --app-<id>-* variables for their body content, but cannot ship global CSS or override the host --vx-* palette. React hooks like useAppContext and usePipelineStatus live at the sibling @veltrixsecops/app-sdk/hooks subpath — kept off the SDK root entry so pipeline handlers stay React-free.

Install & Setup

The kit ships inside the SDK — install it as a dev dependency (no separate UI package, Tailwind preset, or tokens stylesheet to wire up; theming comes from the host at runtime):

npm install --save-dev @veltrixsecops/app-sdk

Then compose your page body from the /ui subpath:

import { Card, CardHeader, CardBody, Button, Badge } from '@veltrixsecops/app-sdk/ui'

export function IndexesPage() {
  return (
    <Card>
      <CardHeader>Indexes <Badge variant="success">Deployed</Badge></CardHeader>
      <CardBody>
        <Button variant="primary">New index</Button>
      </CardBody>
    </Card>
  )
}

These render in the platform design system and pick up the tenant theme (light/dark) and your app's branding automatically via the host's CSS-token bridge — no styling needed.

React Singleton Rule

There must be exactly one copy of React on the page. The CLI's client bundler handles this for you: it marks react, react-dom, react/jsx-runtime, and every @veltrixsecops/app-sdk import (root and subpaths, including /ui and /hooks) as externals, shimming them to the host portal's runtime at load time — so your components render inside the host React tree and never bundle their own copy.

Two React copies = broken hooks

If an app bundles its own React, hooks throw "Invalid hook call" and context (theme, app context) silently fails to cross the boundary. The client-bundle tooling enforces the externals contract so the host's singleton is always used.

// what the CLI bundler externalizes (handled for you at `veltrix package` time)
external: [
  'react', 'react-dom', 'react/jsx-runtime',
  '@veltrixsecops/app-sdk', '@veltrixsecops/app-sdk/ui', '@veltrixsecops/app-sdk/hooks',
]
// → all resolved from the host portal at runtime via globalThis.__VELTRIX_APP_RUNTIME__

Theming & Tokens

Color is defined once as semantic design tokens — space-separated RGB triples so Tailwind can apply opacity modifiers. Components consume them through utility classes (bg-primary, text-content-secondary, border-border), never hardcoded hex. Dark mode flips the "soft" tokens (subtle tints, surfaces, borders, text); solid brand bases stay constant.

Token group Variables Tailwind utility
Brand / semantic --vx-color-primary, -hover, -active, -foreground, -subtle (also success, warning, danger, info) bg-primary, text-primary-foreground, bg-primary-subtle
Surfaces --vx-color-surface, -raised, -overlay, -hover, -sunken bg-surface, bg-surface-raised
Borders --vx-color-border, --vx-color-border-strong border-border, border-border-strong
Content (text) --vx-color-content-primary, -secondary, -tertiary, -disabled, -inverse text-content-primary, text-content-secondary

Dark mode is triggered by either a .dark class or [data-theme="dark"] on the root element — a consumer can use whichever convention it prefers. The host owns these --vx-* values; tenant branding is applied there, and every component follows automatically (values are space-separated RGB triples so Tailwind opacity modifiers keep working):

/* host-owned — the portal sets these; apps read them, they do not redefine them */
:root {
  --vx-color-primary: 79 70 229;          /* indigo-600 instead of the blue default */
  --vx-color-primary-hover: 67 56 202;
}

.dark, [data-theme='dark'] {
  --vx-color-surface: 17 24 39;           /* dark page background */
}

Customization limits

Apps do not ship global CSS and cannot override the host --vx-* palette or the chrome — that is what keeps the portal coherent across every vendor's app. For their own body content, apps may declare app-scoped variables namespaced --app-<id>-*:

/* inside an app's own page body — app-scoped, never global */
.app-splunk-enterprise .chart {
  --app-splunk-enterprise-accent: var(--vx-color-primary);   /* stay on-theme */
  border-color: var(--vx-color-border);
}

You don't ship these tokens yourself — the host portal defines the --vx-* palette on :root, and the /ui components read them at runtime. Your app only opts in to the theme (and may add its own --app-<id>-* variables for body content).

Next Steps

  • App Development — how client.pages contributes navigation today.
  • SDK Reference — the server-side contracts that stay React-free.
  • CLI Reference — the tooling that will build and sync client bundles.