Guidance for producing Dirigible UI views/perspectives that ship in
components/ui/* and render through BlimpKit (the AngularJS-on-Fundamental-Styles
component library bundled at /webjars/blimpkit__blimpkit/dist/blimpkit.min.js,
Angular module name blimpKit).
Three parts:
- Five immutable rules - the non-negotiable house style. Read first.
- Recent lessons from production fixes - what to do (and not do) when authoring a new view, distilled from the latest UI cleanups on monitoring views (commits
3fd93aa6,e0113335,68cb628e). - BlimpKit context extracted from
CLAUDE.mdandCLAUDE_BPMN.md- gotchas, available components, and the iframe wiring recipe.
Read part 0 before doing anything else. Read part 1 before writing a single line of HTML/CSS in a new view. Read part 2 before reaching for <style>, jQuery, or a Bootstrap-3 idiom.
These override every other instinct. If a rule below conflicts with a pattern you see elsewhere in the codebase, the rule wins - the older code is a candidate for cleanup, not a precedent.
-
Perspective icons have no internal padding. The
<svg>content area must extend to the edges of theviewBox. The BlimpKit-rendered icon slot draws its own spacing; padding inside the SVG makes the icon visibly smaller than its neighbours in the sidebar and produces an inconsistent rail. Trim any internal margin out of the path data when adapting a third-party icon. -
No em dashes. Use the standard hyphen-minus (
-) only. Em dashes (—) and en dashes (–) are out. We write software, not romance novels - in prose, code comments, commit messages, and Markdown alike. If you copy text from a chat or doc, sweep it for—before saving. -
Custom CSS classes only when there is no other option. First reach: a BlimpKit utility class (
bk-text--subtitle,bk-font--small,bk-vbox,bk-box--gap,bk-padding--tiny, etc.). Second reach: a Fundamental-Styles class (fd-*). Last reach: a custom class scoped to the view - and only when none of the above expresses what you need. Adding a.metrics-toolbar-subrule that just sets a smaller font and a muted color is the canonical anti-pattern - that's<span class="bk-text--subtitle bk-font--small">and the rule was unnecessary. -
Never hard-code colors, and prefer BlimpKit variables over SAP-legacy ones. Not in hex, not in rgb, not in
<path fill="…">. The lookup order:- A BlimpKit utility class that already maps to the right theme variable (
bk-color--negative,bk-color--positive,bk-color--critical,bk-color--informative,bk-text--subtitle,bk-color-bg--negative, etc.). This is the strongly preferred form. - A BlimpKit theme variable (semantic, short names defined in
/webjars/blimpkit__blimpkit/dist/css/themes/blimpkit-light.css+…-dark.css):var(--negative)/var(--positive)/var(--warning)/var(--information)(each with-foreground,-hover,-activevariants),var(--background),var(--foreground),var(--muted),var(--muted-foreground),var(--accent),var(--border),var(--card),var(--object-background),var(--shellbar-background),var(--sidebar),var(--table-header),var(--font-sans),var(--font-mono),var(--input-background),var(--scrollbar). See the full list in §2.7. - A
--sap*legacy variable only when nothing above matches. The--sap*shim is still shipping for backwards compatibility but is being phased out - reaching forvar(--sapNegativeColor)whenvar(--negative)exists is the wrong choice. Anything you write that still saysvar(--sap*)should have a comment explaining why no BlimpKit equivalent worked.
Hard-coded colors break dark mode, theme switching, and contrast guarantees. The same rule kills inline color styles on dashboards (
color: #b00) and inlinefill="#000000"on perspective SVGs. - A BlimpKit utility class that already maps to the right theme variable (
-
Use BlimpKit components instead of building view-local equivalents. Before writing a
<div class="my-tile">shell with a custom CSS look, search the BlimpKit cheat sheet in §2.3 - the component you need almost certainly exists (bk-card,bk-object-status,bk-progress-indicator,bk-list,bk-message-page,bk-popover, etc.). The pattern below is what you want:<!-- WRONG: invented a custom CSS class for what is just a subtitle --> <style>.metrics-toolbar-sub { font-size: 0.7rem; color: var(--sapContent_LabelColor, #6a6d70); }</style> <span class="metrics-toolbar-sub" ng-show="lastUpdated">{{lastUpdated | date:'HH:mm:ss'}}</span> <!-- RIGHT: BlimpKit utility classes do the same job, no custom CSS --> <span class="bk-text--subtitle bk-font--small">{{lastUpdated | date:'HH:mm:ss'}}</span>
-
For dialogs, use
DialogHub- never put<bk-dialog>markup in a view. Dirigible has a platform-wide dialog system: every view holds anew DialogHub()instance and callsDialogs.showAlert(...)/Dialogs.showDialog(...)/Dialogs.showFormDialog(...)/Dialogs.showWindow(...)/Dialogs.showBusyDialog(...). The shell listens for these messages and renders the dialog with consistent chrome, focus management, and theming. Putting raw<bk-dialog>markup inside a view is what broke dialogs in the old BPMN editor - don't repeat it. Details in §2.4 below; the<bk-dialog>directive itself is reserved for the platform-core shell that hosts the dialog, not for view authors.
Apply these checks against your own diff before requesting review. A reviewer who finds an em dash, a custom CSS class that replicates a BlimpKit utility, a hex color in a perspective, or a <bk-dialog> tag in a view template should bounce the PR.
These three commits replaced hand-rolled CSS, ad-hoc pills/bars/tiles, and Bootstrap-leftover layout with BlimpKit-native equivalents. Treat them as the canonical pattern for new monitoring/dashboard/list views.
If a layout primitive exists as a bk-* directive, use it. The monitoring-view fix deleted ~350 lines of bespoke CSS - .jvm-tile, .jvm-bar, .jvm-state-pill, .metrics-row, .jvm-threads-filters, custom --sap*-keyed colors - and replaced them with their BlimpKit counterparts.
| Don't write | Use instead |
|---|---|
<div class="jvm-tile"><div class="jvm-tile-label">…</div><div class="jvm-tile-value">…</div></div> |
<bk-card card-type="object"><bk-card-content class="bk-vbox bk-box--gap">…</bk-card-content></bk-card> |
<div class="jvm-bar"><div class="jvm-bar-fill warn" style="width: 80%"></div></div> |
<bk-progress-indicator current-value="80" no-label="true" state="critical" aria-label="…"></bk-progress-indicator> |
<span class="jvm-state-pill jvm-state-RUNNABLE">RUNNABLE</span> |
<span bk-object-status status="positive" text="RUNNABLE" inverted="true"></span> |
<div class="metrics-row"><span>…</span><span>…</span></div> |
<bk-list compact="true"><bk-list-item interactive="true"><bk-list-title>…</bk-list-title><bk-list-secondary>…</bk-list-secondary></bk-list-item></bk-list> |
<div class="jvm-section-title">…</div> |
<h2 class="bk-text--subtitle bk-font--small bk-padding--tiny fd-margin--none">…</h2> |
Custom .jvm-threads-empty cell for "no rows" |
<td bk-table-cell no-data="true">No threads…</td> |
<bk-toolbar-label>Title</bk-toolbar-label> |
<bk-toolbar compact="true" has-title="false"><span>Title</span>…</bk-toolbar> |
The lone surviving CSS in the cleaned-up views is a one-rule grid that bk-card can't express on its own:
.jvm-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(13.75rem, 1fr));
}That's the threshold - when BlimpKit + the bk-* utility classes (see §1.2) cover it, drop the <style> block entirely.
The cleanups standardise on these utility classes - they ship with BlimpKit and replace the hand-written font-size: 0.75rem; color: var(--sapContent_LabelColor); … patterns scattered across the old views.
| Class | What it does |
|---|---|
bk-vbox / bk-hbox |
Flex column / row |
bk-box--gap |
Standard gap between flex children |
bk-box--items-center |
Cross-axis center |
bk-box--space-between |
Justify space-between |
bk-flex--wrap |
Allow flex wrapping |
bk-flex-1 |
flex: 1 |
bk-full-height |
Fill parent height |
bk-center |
Center content (for empty states) |
bk-padding--tiny |
Standard small padding |
bk-text--subtitle |
Muted label color |
bk-font--small / bk-font--normal / bk-font--large / bk-font--h4 |
Type scale |
bk-font--bold |
Bold weight |
bk-font--mono |
Monospace font (resolves to var(--font-mono)) |
bk-color--negative / --positive / --critical / --informative |
Semantic text colors. These wire to BlimpKit's var(--negative) / var(--positive) / var(--warning) / var(--information) theme variables - reach for the class first; only fall back to var(--negative) etc. when the class doesn't fit (e.g. inline SVG fill). Never var(--sapNegativeColor). |
bk-color-bg--negative / --positive / --critical / --informative |
Same semantics, applied as background-color |
Rule of thumb: if you're typing <style> or style="…" for typography, color, spacing, or flex layout, you're recreating something BlimpKit already ships. Look at components/ui/perspective-monitoring/ (post-cleanup) for the reference look.
A naïve "label + select, label + select, button" row reflows by element on narrow viewports, so the label "State" can end up on one line and its <bk-select> on the next. Fix from commit 68cb628e: wrap each label+control in its own bk-hbox so they wrap as a group.
<div class="bk-hbox bk-flex--wrap bk-box--items-center bk-box--gap">
<div class="bk-hbox bk-box--items-center bk-box--gap">
<label class="bk-text--subtitle" for="i-state">State</label>
<bk-select id="i-state" compact="true" …></bk-select>
</div>
<div class="bk-hbox bk-box--items-center bk-box--gap">
<label class="bk-text--subtitle" for="i-daemon">Daemon</label>
<bk-select id="i-daemon" compact="true" …></bk-select>
</div>
<bk-button compact="true" state="transparent" glyph="sap-icon--clear-filter" label="Clear" …></bk-button>
<div style="flex: 1"></div>
<span class="bk-text--subtitle bk-font--small">Showing {{ filtered.length }} of {{ all.length }}</span>
</div>Always pair <label for="…"> with an id on the BlimpKit control - keyboards/screen readers depend on it.
<bk-object-status> is the right component for short status labels (thread state, build status, severity). Two things bit the monitoring view:
- Custom pill CSS is wrong. The old
.jvm-state-pill { padding: 1px 6px; border-radius: 9999px; … }reinvented whatbk-object-statusalready draws. Replace with<span bk-object-status status="…" text="…" inverted="true">. - The text needs
white-space: nowrapto stop wrapping inside the colored pill. Add it inline:<span bk-object-status status="…" style="white-space: nowrap;" text="…" inverted="true">. Without it, multi-word statuses (TIMED_WAITING) break across two lines inside the pill and the chip stretches vertically.
Map domain states to BlimpKit semantic states explicitly in the controller - don't hard-code colors:
$scope.getObjectState = (state) => {
if (state === 'RUNNABLE') return 'positive';
if (state === 'BLOCKED' || state === 'TERMINATED') return 'negative';
if (state === 'WAITING' || state === 'TIMED_WAITING') return 'informative';
if (state === 'NEW') return ''; // neutral
};Same applies to bk-progress-indicator - pass state="critical" / state="negative" / '' instead of 'warn' / 'danger' / ''. The named states are tied to the theme; the custom strings just produced unstyled bars.
Commit e0113335 removed <bk-list-item style="height: 2rem"> everywhere and added compact="true" to the parent <bk-list>. The list directive renders its rows compactly through Fundamental-Styles when the attribute is set; inline height overrides the row's vertical padding and produces clipped text.
<!-- WRONG: forces row height, clips descenders -->
<bk-list>
<bk-list-item style="height: 2rem" interactive="true" …>…</bk-list-item>
</bk-list>
<!-- RIGHT: uses BlimpKit's compact density -->
<bk-list compact="true">
<bk-list-item interactive="true" …>…</bk-list-item>
</bk-list>For a click-to-sort column header, mark the cell interactive="true" and render a sap-icon--sort / sap-icon--up / sap-icon--down icon inside, not a Unicode caret.
<th bk-table-header-cell interactive="true" ng-click="setSort('id')">
<div class="bk-hbox bk-box--gap bk-box--space-between bk-box--items-center">
<span>#</span>
<span class="sap-icon sap-icon--{{sortIcon('id')}} bk-font--normal"
role="img" aria-label="{{ sortIconLabel('id') }}"></span>
</div>
</th>$scope.sortIcon = (key) => {
if ($scope.sort.key !== key) return 'sort';
return $scope.sort.reverse ? 'down' : 'up';
};
$scope.sortIconLabel = (key) => {
if ($scope.sort.key !== key) return 'Sort';
return $scope.sort.reverse ? 'Descending' : 'Ascending';
};Remember: ng-class on the <th bk-table-header-cell> element itself breaks (string-concatenates with the directive's own internal ng-class). Push conditional classes onto a child <span>. (CLAUDE.md gotcha - restated in §2.)
-
Use
outer-borders="horizontal"(or"top") on<table bk-table>so the table has top/bottom rules but no left/right rules - cleaner thanouter-borders="none"and matches the monitoring views' final look. -
Render the empty-state row through the directive's own attribute, not a custom
class="…-empty"CSS:<tr bk-table-row ng-if="!filteredThreads.length && !loading"> <td bk-table-cell no-data="true"> {{ threads.length ? 'No threads match the current filters.' : 'No threads to display.' }} </td> </tr>
-
Wrap a horizontally-overflowing table in
<div bk-scrollbar>so the BlimpKit-styled scrollbar shows up.
The current pattern (post-cleanup):
<bk-toolbar compact="true" has-title="false">
<span>View Title</span>
<bk-toolbar-spacer></bk-toolbar-spacer>
<span class="bk-text--subtitle bk-font--small">Last updated {{lastUpdated | date:'HH:mm:ss'}}</span>
<bk-toolbar-separator></bk-toolbar-separator>
<bk-button state="transparent" glyph="sap-icon--refresh" …></bk-button>
…
</bk-toolbar>bk-toolbar-label is gone from these views - has-title="false" + a child <span> renders the same chrome without the directive's extra wrapping.
A <bk-select> is a form input. Toolbars are not forms. Toolbars host quick actions and settings expressed as buttons (refresh, pause, toggle, "change interval"). Dropping a <bk-select> into a toolbar visually clashes with the surrounding buttons and behaves like a form widget where the user expects an action.
The only inputs that are legitimate in a toolbar are <bk-input type="text"> and <bk-input type="search"> for inline filtering/searching - those still count as quick actions because they steer the toolbar's view.
For a settings picker like "refresh interval", the right shape is a <bk-button> that opens a <bk-popover> + <bk-menu>. The button surfaces the current choice ("Every 5s"); the menu lists the alternatives:
<bk-popover>
<bk-popover-control>
<bk-button state="transparent"
label="{{refreshIntervals[refreshIntervalSeconds]}}"
is-menu="true" aria-haspopup="menu" aria-controls="refreshIntervals"></bk-button>
</bk-popover-control>
<bk-popover-body id="refreshIntervals" align="bottom-right">
<bk-menu aria-label="refresh interval options">
<bk-menu-item title="{{value}}" ng-click="setRefreshInterval(key)"
ng-repeat="(key, value) in refreshIntervals track by key"></bk-menu-item>
</bk-menu>
</bk-popover-body>
</bk-popover>$scope.refreshIntervalSeconds = 5; // number, not string
$scope.refreshIntervals = { 2: 'Every 2s', 5: 'Every 5s', 10: 'Every 10s', 30: 'Every 30s' };
$scope.setRefreshInterval = (value) => { $scope.refreshIntervalSeconds = value; $scope.restartTimer(); };Store numeric model values as numbers, not strings - the old '5' required parseInt on every read. Pure numbers compare cleanly and pass to $interval directly.
The cleanup changed style="min-width: 110px" → style="min-width: 7.5rem", 200px → 12.5rem, etc. Reasoning: BlimpKit's typography and density scale with the root font size; px widths break when the user (or the theme) changes the base size. If you must inline a width, divide by 16 and use rem.
When adding a perspective icon under components/ui/perspective-*/src/main/resources/META-INF/dirigible/.../images/*.svg:
- No internal padding around the artwork (rule 1 of §0). Path data must extend to the edges of the
viewBox- a 24x24 viewBox uses x/y in roughly[0..24], not[2..22]. The IDE sidebar slot draws its own spacing; an SVG that pads itself looks visibly smaller than neighbouring icons and the rail goes inconsistent. The cleanup in commit3fd93aa6redrew the monitoring icon at full 24x24 extent (m20.4 0h-16.8a3.6 3.6 0 0 0-3.6 3.6v16.8a3.6 3.6 0 0 0 3.6 3.6h16.8a3.6 3.6 0 0 0 3.6-3.6v-16.8...) and moved the body decoration to match. - Strip every
fill="…"attribute from<path>/<circle>/etc. - the CSS inblimpkit.cssstyles the SVG viafill: var(--fdVerticalNav_Icon_Color, #303030)and switches tovar(--sapSelectedColor)on the active state. A hard-codedfill="#000000"(the default when you paste from an icon set) locks the icon to black and breaks dark-theme adaptation. (Rule 4 of §0 - never hard-code colors.) - Use
viewBox="0 0 24 24"withwidth="512" height="512"on the outer<svg>. (stroke-width=".99999"is the established style but doesn't affect rendering through this CSS.) - Add
xml:space="preserve"to the outer<svg>tag - that was the cleanup in commit3fd93aa6. - Don't add explicit
stroke-widthon every path if the design doesn't need outlines - the monitoring icon cleanup removed them and the icon still renders correctly with whatever stroke the CSS provides.
When a new BlimpKit feature is needed, bump the <blimpkit.version> property in the root pom.xml (currently 2.3.0 after commit 3fd93aa6 bumped from 2.2.2). Don't override per-module - the version is centralised so every UI module gets the same bk-* directives.
This section consolidates everything BlimpKit-related from the existing CLAUDE.md and CLAUDE_BPMN.md notes so a UI-focused author has one place to look. The originals stay authoritative for non-UI material.
The IDE shell and most editor perspectives render through BlimpKit, an AngularJS component library shipped as a webjar. BlimpKit's sources live in an external repo (https://github.com/blimpkit/blimpkit.github.io); inside this repo it is consumed only as the published artifact, served from /webjars/blimpkit__blimpkit/dist/. The Angular module name is blimpKit (camelCase). The version is pinned in the root pom.xml as <blimpkit.version> (currently 2.3.0).
The five files you actually link against:
| Path | Purpose |
|---|---|
/webjars/blimpkit__blimpkit/dist/blimpkit.js |
Unminified BlimpKit component library (for debugging). |
/webjars/blimpkit__blimpkit/dist/blimpkit.min.js |
Minified BlimpKit (production link). |
/webjars/blimpkit__blimpkit/dist/css/blimpkit.css |
Main CSS: theme-variable mapping, bk-* utility classes, base component styles. |
/webjars/blimpkit__blimpkit/dist/css/themes/blimpkit-light.css |
Light-theme variable definitions. Sets --background, --foreground, --negative, --positive, etc. on :root. |
/webjars/blimpkit__blimpkit/dist/css/themes/blimpkit-dark.css |
Dark-theme variable definitions (same variable names, different values). |
Plus the SAP-icons + 72 body font @font-face declarations at /services/web/platform-core/ui/styles/fonts.css. The IDE shell pulls all of these automatically via the platform-links injection mechanism (§2.5); only standalone iframes have to link them by hand.
There is no components/ui/platform-core/.../blimpkit/ source folder inside this repo any more - older notes still reference it, ignore them. To audit a bk-* component's behaviour, read /tmp/blimpkit.css (or grep the upstream repo on GitHub), not anything inside components/.
These are the ones that have already burned someone.
-
<bk-checkbox>is invisible without<bk-checkbox-label>.bk-checkboxcompiles to a bare<input type="checkbox" class="fd-checkbox">. Fundamental-Styles'.fd-checkboxrule hides the native input (opacity:0; position:absolute) on the assumption that a sibling<bk-checkbox-label>will draw the visible square via its.fd-checkbox__checkmark::before pseudo. A lone<bk-checkbox>is therefore a working click target with zero visible chrome - easy to ship and never catch in code review. Pair it:<bk-checkbox id="x" ng-model="…"></bk-checkbox> <bk-checkbox-label for="x" empty="true">…</bk-checkbox-label>
Use
empty="true"when the surrounding markup already labels the row so the label provides just the checkmark. -
<bk-dialog>is a shell-internal directive, not for view authors. UseDialogHubinstead (see §2.4). The two notes below apply only to maintainers working insideplatform-core's dialog shell or to the legacy BPMN/Flowable editor migration. For a new view, do not write<bk-dialog>at all.- Isolate scope. You can't put
ng-controller="…PopupCtrl"on the dialog element itself - Angular throws "Multiple directives [bkDialog, ngController] asking for new/isolated scope on: ". Wrap with a thin<div ng-controller="…">and put<bk-dialog visible="…">inside. - Toggled via the
visiblebinding, not a.modal('show')plugin.<bk-dialog visible="modal.visible">watches the expression and addsfd-dialog--activewhen true. No backdrop element is added (the dialog's own.fd-dialog--activeoverlay handles z-index + dimming). To dismiss programmatically: flip the bound flag inside an$apply; let the directive's digest cycle remove the--activeclass; then$timeout~300ms before tearing down the scope so the close animation completes.
- Isolate scope. You can't put
-
<bk-select>doesn't supportng-options. Use<bk-option ng-repeat>instead - text via thetextattribute, model value viavalue. Example:<bk-option ng-repeat="opt in items" text="{{opt.name}}" value="opt.id"></bk-option>
When the select sits in a parent with
overflow:hidden(a dialog, a sidebar), adddropdown-fixed="true"so the menu floats viaposition:fixedinstead of being clipped. -
<bk-option>'stextandvaluebind differently -text: '@'is interpolation (usetext="{{ expr }}"or a literal),value: '<'is a one-way expression (usevalue="expr", nevervalue="{{ expr }}"). Mixing them up is the canonical bug for this directive:value="{{s}}"makes Angular try to parse{{s}}as a JS expression, the directive's link silently fails, and the dropdown shows raw{{ text }}from the unlinked template (one ghost item per ng-repeat iteration). Fix:value="s".value="user"evaluates$scope.user, not the string"user"- every option ends up with the sameundefinedvalue and selection becomes a no-op. For string literals, quote inside:value="'user'". For the empty default option,value="''", notvalue=""(which is the undefined-expression).- Numeric literals (
value="2") and loop variables (value="s") are already expressions - leave them unquoted. Numbers stay numbers, soselectedValue === '2'will fail; either store as numbers on the model or coerce in the controller.
-
Perspective SVG icons inherit
fillfrom CSS - don't hard-codefillon the path.blimpkit.cssstyles.fd-list__navigation-item i.bk-icon--svg svgwithfill: var(--fdVerticalNav_Icon_Color, #303030)(andvar(--sapSelectedColor)on the active state). Addingfill="#000000"locks the icon to black and breaks dark-theme adaptability. Strip the fill attribute or setfill="currentColor". -
<bk-input>/<bk-textarea>/<bk-button>usereplace:true. The attributes you write on the directive element (ng-model, ng-blur, ng-keypress, ng-disabled, custom directives likeauto-focus/select-text) end up on the underlying native<input>/<textarea>/<button>, so existing controller code keeps working unchanged after migrating native form controls tobk-*. ng-model binds against the parent scope - the isolate scopebk-inputdeclares only ownscompact/state/glyph. -
Don't put
ng-classon areplace:truedirective element that already has its ownng-class.bk-table-header-cell,bk-table-cell, and most layout-y BlimpKit directives template as<th ng-class="getClasses()" …>- Angular's attribute merge string-concatenates duplicateng-classvalues, producing nonsense likeng-class="{ sorted: sort.key === 'id' } getClasses()". The page then throws$parse:syntaxat compile time and the row never renders. (classmerges cleanly - onlyng-classis broken - soclass="no-sort"on a<th bk-table-header-cell>works fine.) Fix: push the conditional class onto a child element instead of the directive root:<th bk-table-header-cell ng-click="…"> <span class="sort-caret" ng-class="{ active: sort.key === 'id' }">{{ caret() }}</span> </th>
Same applies for anything else with a
replace:true+ng-classtemplate (auditcomponents/ui/platform-core/.../blimpkit/*.jsfor the pattern before addingng-classto abk-*directive). -
The
blimpKitmodule's.config()block disables three$compileProviderflags.cssClassDirectivesEnabled(false),commentDirectivesEnabled(false), anddebugInfoEnabled(false)are flipped at module-load. Saves per-element scope-tracking overhead in production. The last flag breaks Selenide-style debugging that callsangular.element(node).scope()- Angular stops attaching scope refs to DOM nodes, so the lookup returnsundefined. If your app or its integration tests rely on that, re-enable the flags in a.config(['$compileProvider', …])block of your own - module config blocks run in dependency order, soblimpKit's flips happen first and your override sticks. -
SAP-icons + the "72" body font live in platform-core's
fonts.css. Every BlimpKit-using page needs<link rel="stylesheet" href="/services/web/platform-core/ui/styles/fonts.css">. Without it.sap-icon--*glyphs render as tofu squares because the@font-face { font-family: "SAP-icons"; … }declaration is missing. The IDE shell loads this automatically via theplatform-linksinjection mechanism (see §2.5); standalone iframes (editor-bpm, embedded views) have to add the link tag explicitly. Other@font-facerules in the same file declare the body font:"72"(Regular / Light / Bold),"72-Light","72-Bold","72Mono-Regular","72Mono-Bold", plus"BusinessSuiteInAppSymbols"and"SAP-icons-TNT". -
Test-selector changes after a BlimpKit migration. Native
<input class="form-control">→<input class="fd-input fd-input--compact">.<div class="modal in">(Bootstrap-3 visible) →<section class="fd-dialog fd-dialog--active">.body.modal-openand.modal-backdropare NOT set by<bk-dialog>- drop assertions on those, the active overlay handles its own dimming. When fixing Selenide tests that look at.modal-header .close, switch to.fd-dialog__header .fd-button(or scope to the dialog withsection.fd-dialog--active button.fd-button).
~106 bk-* components ship in BlimpKit. The ones a new view typically needs:
| Need | BlimpKit |
|---|---|
| Modal / popup dialog | Use DialogHub from a view, not the directive. See §2.4. The bk-dialog* directives are shell-internal. |
| Form fields | bk-form-group, bk-form-item, bk-form-label, bk-input, bk-textarea, bk-select + bk-option, bk-checkbox + bk-checkbox-label, bk-radio, bk-switch, bk-step-input. Form inputs belong in forms - keep them out of toolbars (see §1.9). |
| Buttons / icons | bk-button (with glyph="sap-icon--…"), bk-segmented-button |
| Tables | bk-table, bk-table-header, bk-table-header-cell, bk-table-row, bk-table-body, bk-table-cell |
| Lists / property tables | bk-list (use compact="true"), bk-list-item, bk-list-title, bk-list-secondary, bk-list-navigation-item, bk-form-list |
| Cards / tiles | bk-card, bk-card-content (use card-type="object" for KPI tiles) |
| Status / progress | bk-object-status (with status="positive"/"negative"/"critical"/"informative"), bk-progress-indicator (with state="critical"/"negative") |
| Tabs | bk-icon-tab-bar, bk-icon-tab-bar-tab, bk-icon-tab-bar-panel |
| Validation messages | bk-form-input-message, bk-form-message |
| Action bars | bk-toolbar, bk-toolbar-spacer, bk-toolbar-separator, bk-bar, bk-bar-element |
| Popovers / menus | bk-popover, bk-popover-control, bk-popover-body, bk-menu, bk-menu-item |
| Empty / error state | bk-message-page, bk-message-page-title, bk-message-page-subtitle |
| Scroll container | <div bk-scrollbar> (custom-styled scrollbar) |
Reference real-world usage:
components/ui/perspective-monitoring/,view-jvm-monitoring,view-jvm-threads,view-monitoring-metrics- the canonical BlimpKit-native dashboard look after the §1 cleanups.components/ui/view-jobs/,components/ui/view-security/,components/ui/view-repository/- production patterns forDialogHubusage (showAlert,showDialog,showFormDialog,showWindow).components/ui/perspective-processes/- BPM-adjacent perspective that is BlimpKit-aligned.
(Note: editor-monaco is not a BlimpKit reference - it predates the migration and is a known cleanup target. Don't copy from it.)
Dirigible's dialog system is platform-wide and message-bus-driven. A view does not render <bk-dialog> markup; it instantiates a DialogHub and calls one of its show* methods. The shell (platform-core) listens for the resulting platform-bus messages and renders the dialog itself with consistent chrome, focus management, theming, and z-index handling. This is what gives every view's dialog the same look without each view having to know how to draw one.
The class lives at components/resources/platform-core/src/main/resources/META-INF/dirigible/platform-core/ui/platform/dialog-hub.js. It is loaded automatically by the ng-view platform-links bundle (§2.5), so you don't link it yourself in a normal view - just new DialogHub().
Wiring it into a controller (jobs view pattern):
const jobsView = angular.module('jobs', ['blimpKit', 'platformView']);
jobsView.constant('Dialogs', new DialogHub()); // one instance, injected as 'Dialogs'
jobsView.controller('JobsController', ($scope, Dialogs) => {
Dialogs.showAlert({
title: 'Job state error',
message: 'There was an error while trying to enable the job.',
type: AlertTypes.Error, // 'confirmation' | 'error' | 'success' | 'warning' | 'information'
preformatted: false,
});
});The five show* methods you actually need:
| Method | When to reach for it |
|---|---|
showAlert({title, message, type, preformatted?, buttons?}) |
A quick notification or one-shot question. Returns a promise resolving to the clicked button's id only when buttons is provided; default is a single "Close". type uses the AlertTypes enum. |
showDialog({header?, title, subheader?, message, preformatted?, buttons?, closeButton?}) |
A richer dialog with header/subheader and a custom button row. Same promise contract as showAlert. |
showBusyDialog(message) / closeBusyDialog() |
Spinner overlay while a long action runs. Pair every showBusyDialog with a matching closeBusyDialog (try/finally). |
showFormDialog({header?, title, form, submitLabel?, cancelLabel?, width?, height?, …}) |
Auto-rendered form (input / textarea / checkbox / radio / dropdown) with submit/cancel. form is an object { <id>: <FormItem> }. Returns a promise resolving to { <id>: value }. Use this instead of hand-writing a "user/password/confirm" dialog. |
showWindow({hasHeader?, header?, title?, id, params?, width?, height?, …}) / closeWindow() |
Opens another registered view inside a windowed iframe. id is the view id (registered through the perspective config); params becomes data-parameters on the embedded iframe. Use this when the dialog content is itself a view (e.g. an editor in a popup). |
FormItem supports common properties (label / value / required / disabled / focus / errorMsg) plus per-control-type ones (type for input, options for dropdown, rows for textarea, min/max/step for numeric, inputRules for regex validation, enabledOn / disabledOn / visibleOn / hiddenOn for cross-field dependencies). See the JSDoc at the top of dialog-hub.js for the full list before writing custom checks.
Don't:
- Don't write
<bk-dialog>markup in a view template. That's what broke the BPMN editor's dialogs - the editor included raw<bk-dialog>instead of routing throughDialogHub, and lost the platform's dialog plumbing. - Don't build a custom "modal" with
<div class="modal">/ Bootstrap-3 patterns. The legacy editor still has those; they are not the pattern for new code. - Don't subscribe to platform dialog topics from a view to render the dialog yourself. The shell is the consumer. The
onAlert/onDialog/onWindowlisteners onDialogHubexist so the shell can render dialogs - not so views can re-implement them.
Looking at any non-iframe perspective HTML you'll see a single <meta name="platform-links" category="ng-view,ng-perspective">-style tag in the <head>. HtmlPlatformLinksInjector (in components/engine/engine-web/.../HtmlPlatformLinksInjector.java) reads it at request time, walks the category list, and replaces the meta tag with the bundle of <link> and <script> tags registered for those categories.
Categories are defined in components/engine/engine-web/src/main/resources/platform-links.json:
ng-view- heavyweight bundle (jQuery, AngularJS, all the platform hubs, BlimpKit, Fundamental-Styles, fonts.css)ng-perspective- adds split + layoutng-editor- adds workspace + repository hubs- … etc.
Adding new shared platform code → add it to this JSON, not to every perspective HTML.
When building a self-contained editor or view that doesn't go through platform-links injection (e.g. the editor-bpm iframe), the iframe HTML has to link every script + stylesheet explicitly. Minimum set for BlimpKit to work:
<link rel="stylesheet" href="/services/web/platform-core/ui/styles/fonts.css">
<link rel="stylesheet" href="/webjars/fundamental-styles/dist/fundamental-styles.css">
<link rel="stylesheet" href="/webjars/blimpkit__blimpkit/dist/css/blimpkit.css">
<!-- jQuery + AngularJS first, then: -->
<script src="/webjars/angular-aria/angular-aria.min.js"></script>
<script src="/webjars/blimpkit__blimpkit/dist/blimpkit.min.js"></script>
<!-- then your app's scripts -->Plus add 'blimpKit' to the app's module-dep array in its main app.js:
angular.module('myApp', ['ngRoute', 'blimpKit', /* … */]);Side-effects to know about:
- The
blimpKitmodule's.config()block calls$compileProvider.cssClassDirectivesEnabled(false)/commentDirectivesEnabled(false)/debugInfoEnabled(false). Those are app-global.debugInfoEnabled(false)is a real regression for integration tests that reach into Angular viaangular.element(node).scope(). Re-enable the flags in a.config(['$compileProvider', …])block in your ownapp.js- dependency config blocks run first, so the override sticks. - Loading
blimpkit.min.jsBEFORE yourapp.jsis mandatory -app.js'sangular.module(...)references'blimpKit', which has to be registered already. - Without
fonts.csssap-icon--*glyphs render as tofu squares.
Variable preference order (also stated in §0 rule 4):
-
A
bk-*utility class that already wires to the right variable (bk-color--negative,bk-text--subtitle,bk-color-bg--informative, etc.). -
A BlimpKit theme variable declared in
blimpkit-light.css/blimpkit-dark.css. These are the canonical theme tokens. The set:Group Variables Base surface --background,--foreground,--contrast,--muted,--muted-foreground,--highlight,--border,--border-width,--radius,--radius-controlCards / objects --card,--card-hover,--card-active,--card-border,--object-background,--object-background-alt,--object-background-hover,--object-background-active,--object-foreground,--object-header,--object-header-border,--object-header-foreground,--object-header-shadowAccent --accent,--accent-foreground,--accent-hover,--accent-activeSecondary --secondary,--secondary-foreground,--secondary-border,--secondary-hover,--secondary-active,--secondary-active-borderSemantic state --negative,--negative-foreground,--negative-hover,--negative-active; same-foreground/-hover/-activequartet for--positive,--warning,--informationInputs / buttons / tracks --input-background,--input-background-hover,--input-background-active,--input-border,--input-border-hover,--button-border,--button-shadow,--track-background,--track-background-hover,--track-handle-background,--track-handle-foregroundOutline / focus --outline,--outline-style,--outline-offset,--outline-widthShell chrome --shellbar-background,--shellbar-foreground,--shellbar-shadow,--shellbar-interactive-background,--shellbar-interactive-foreground,--shellbar-interactive-hover,--shellbar-interactive-active,--shellbar-interactive-border,--shellbar-interactive-placeholderSidebar --sidebar,--sidebar-foreground,--sidebar-border,--sidebar-shadow,--sidebar-hover,--sidebar-active,--sidebar-active-hover,--sidebar-active-foregroundTables --table-header,--table-header-foregroundLinks --link,--link-hover,--link-visited,--link-subtle,--link-invertedScrollbars --scrollbar,--scrollbar-thumb,--scrollbar-thumb-hoverInactive / disabled --inactive-background,--inactive-border,--disabled-opacityShadows --shadow-xs,--shadow-sm,--shadow-md,--shadow-lgTypography --font-sans,--font-sans-bold,--font-sans-semibold,--font-mono,--font-mono-bold,--font-sizePopover --popover-arrow-borderDefinitive source:
/webjars/blimpkit__blimpkit/dist/css/themes/blimpkit-light.css(and-dark.css). Both files define the same names with theme-appropriate values, sovar(--foreground)is correct for either theme. -
A
--sap*legacy variable only when nothing above expresses the need. The shim still ships viatheme-blimpkit/css/sap-variables-{light,dark,auto}.cssfor backwards compatibility (BpmnEditorIT's boot station asserts the theme variable is non-empty inside the iframe), but most existing references are migration debt, not a pattern to copy. If you writevar(--sapXxx), leave a comment that explains which BlimpKit variable you tried first and why it didn't work.
Theme-detection class. @media (prefers-color-scheme: dark) is the OS preference, not the Dirigible theme. To target the theme the user picked, write setTheme() to add body.dirigible-theme-{light,dark,auto} and gate your CSS on that class. Reserve prefers-color-scheme for the dirigible-theme-auto fallback.
ui-grid 3.0.0 hard-codes light-mode colors (#fdfdfd / #f3f3f3 / #f0f0ee / #c9dde1 / #d4d4d4). They lose contrast in dark mode. The standard fix lives in editor-bpm/editor-app/theme/style.css near the bottom and uses var(--sapList_*) (Background, AlternatingBackground, HeaderBackground, SelectionBackgroundColor, BorderColor, TextColor). If you add a new ui-grid usage, copy the override block and re-audit selector specificity - the original ui-grid CSS rules ship with background-color: and border-color: that beat unscoped overrides.
- Don't write
<bk-dialog>markup in a view. UseDialogHub(§2.4). The BPMN editor's broken dialogs are exactly this mistake. - Don't put a
<bk-select>(or any other form input besidestype="text"/type="search") in a toolbar. Toolbars hold actions and settings as buttons. (§1.9) - Don't write
var(--sapXxx)whenvar(--xxx)(BlimpKit) or abk-*class would do. The--sap*shim is legacy debt, not the target. (§2.7) - Don't reintroduce angular-strap
bs-select/bs-tooltip/bs-popoverdirectives - they've been ripped out and there's no migration plan back. Use native<select>or<bk-select>in forms, thetitleattribute, or<bk-popover>. - Don't trust Bootstrap-3's
.modal('show')plugin inside iframes - it's unreliable. The drop-in modal service ineditor-bpmexplicitly setselement[0].style.display = 'block'. For new code, route every dialog throughDialogHub. - Don't write per-view CSS for typography, spacing, color, or flex layout when a
bk-*utility class exists. Audit the existing styles incomponents/ui/perspective-monitoring/for the reference look. - Don't copy from
components/ui/editor-monaco/for BlimpKit patterns - it predates the BlimpKit migration and is a known cleanup target.
§0 rules:
- No em dashes (
—) or en dashes (–) anywhere - prose, code, commit messages, Markdown. Plain-only. - No
<bk-dialog>markup in any view template - dialogs go throughDialogHub. - No hex / rgb / inline color values. No
var(--sapXxx)when a BlimpKit class (bk-color--*) or variable (var(--negative), etc.) exists. - Custom CSS classes only where no BlimpKit / Fundamental class fits.
- Perspective SVG icon has no internal padding (paths extend to the
viewBoxedges) and no hard-codedfill. UsesviewBox="0 0 24 24"+xml:space="preserve".
§1 rules (recent monitoring-view cleanups):
- No
<style>block (or one small grid/sizing rule, max). - No inline
style="height: …; padding: …; color: …; font-size: …"- use BlimpKit utility classes. - No hand-rolled "pill" / "tile" / "bar" / "section-title" CSS - use
bk-object-status,bk-card,bk-progress-indicator, headings +bk-text--subtitle. - No Unicode-caret sort indicators - use
sap-icon--sort/up/downwithinteractive="true"on the<th bk-table-header-cell>. - No
<bk-select>(or other form input besidestype="text"/type="search") in a toolbar - use a<bk-button>opening a<bk-popover>+<bk-menu>. - Every
<bk-checkbox>is paired with a<bk-checkbox-label>(withempty="true"when redundant text). - Every
<label for="…">matches anid="…"on its BlimpKit control. - Numeric model values are stored as numbers, not strings.
- Inline widths use
rem, notpx. - Filter rows wrap label+control as a unit (
bk-hboxper pair,bk-flex--wrapon the parent). - Domain → BlimpKit status mapping is in the controller (
positive/negative/critical/informative/''), not hard-coded colors. - Sortable headers, table-empty cells, and scroll containers use the BlimpKit attributes (
interactive="true",no-data="true",<div bk-scrollbar>). - If the view ships in its own iframe (not a perspective),
fonts.cssis linked explicitly and theblimpKitdebug-flag override is in.config(). - If a new BlimpKit version is required, bumped centrally in root
pom.xml(<blimpkit.version>).