Component Refactoring
langflow-ai/langflow
Refactor high-complexity React components in Langflow frontend.
Reproduce a Claude Design prototype or design handoff (HTML/CSS, .dc.html export, tokens, screenshots) inside a Mendix app: build the palette with mxcli theme create --from, then apply classes in…
$ npx skills add mendixlabs/mxcli --skill migrate-design-prototype -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install mendixlabs/mxcli migrate-design-prototype --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/mendixlabs/mxcli.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/mendix/migrate-design-prototype .claude/skills/migrate-design-prototype && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "migrate-design-prototype" agent skill from https://github.com/mendixlabs/mxcli/tree/main/.claude/skills/mendix/migrate-design-prototype into .claude/skills/migrate-design-prototype/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "migrate-design-prototype", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/mendixlabs/mxcli/tree/main/.claude/skills/mendix/migrate-design-prototypeType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add mendixlabs/mxcli --skill migrate-design-prototype -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install mendixlabs/mxcli migrate-design-prototype --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/mendixlabs/mxcli.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.claude/skills/mendix/migrate-design-prototype .agents/skills/migrate-design-prototype && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "migrate-design-prototype" agent skill from https://github.com/mendixlabs/mxcli/tree/main/.claude/skills/mendix/migrate-design-prototype into .agents/skills/migrate-design-prototype/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "migrate-design-prototype", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add mendixlabs/mxcli --skill migrate-design-prototype -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install mendixlabs/mxcli migrate-design-prototype --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/mendixlabs/mxcli.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.claude/skills/mendix/migrate-design-prototype .cursor/skills/migrate-design-prototype && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "migrate-design-prototype" agent skill from https://github.com/mendixlabs/mxcli/tree/main/.claude/skills/mendix/migrate-design-prototype into .cursor/skills/migrate-design-prototype/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "migrate-design-prototype", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/mendixlabs/mxcli.git --path .claude/skills/mendix/migrate-design-prototype--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add mendixlabs/mxcli --skill migrate-design-prototype -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install mendixlabs/mxcli migrate-design-prototype --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/mendixlabs/mxcli.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.claude/skills/mendix/migrate-design-prototype .gemini/skills/migrate-design-prototype && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "migrate-design-prototype" agent skill from https://github.com/mendixlabs/mxcli/tree/main/.claude/skills/mendix/migrate-design-prototype into .gemini/skills/migrate-design-prototype/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "migrate-design-prototype", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install mendixlabs/mxcli migrate-design-prototypeInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add mendixlabs/mxcli --skill migrate-design-prototype -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/mendixlabs/mxcli.git skills-src && mkdir -p .github/skills && cp -r skills-src/.claude/skills/mendix/migrate-design-prototype .github/skills/migrate-design-prototype && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "migrate-design-prototype" agent skill from https://github.com/mendixlabs/mxcli/tree/main/.claude/skills/mendix/migrate-design-prototype into .github/skills/migrate-design-prototype/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "migrate-design-prototype", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add mendixlabs/mxcli --skill migrate-design-prototype -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install mendixlabs/mxcli migrate-design-prototype --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/mendixlabs/mxcli.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.claude/skills/mendix/migrate-design-prototype .opencode/skills/migrate-design-prototype && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "migrate-design-prototype" agent skill from https://github.com/mendixlabs/mxcli/tree/main/.claude/skills/mendix/migrate-design-prototype into .opencode/skills/migrate-design-prototype/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "migrate-design-prototype", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
migrate-design-prototypeReproduce a Claude Design prototype or design handoff (HTML/CSS, .dc.html export, tokens, screenshots) inside a Mendix app: build the palette with mxcli theme create --from, then apply classes in…
Migrate Design Prototype is an agent skill from mendixlabs/mxcli. Reproduce a Claude Design prototype or design handoff (HTML/CSS, .dc.html export, tokens, screenshots) inside a Mendix app: build the palette with mxcli theme create --from, then apply classes in pages with MDL. Use when given a design artefact and asked to make the app look like it.
Its SKILL.md is about 9.8k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Development. The repository describes itself as: Mendix cli tool, a headless way to work with Mendix projects. Enables Mendix projects for use with 3rd party agentic coding tools like Claude Code and Copilot. Includes a… The licence is Apache-2.0.
2 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit a924d11. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
dockerclaudeFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
fonts.googleapis.comw3.orgFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Migrate Design Prototype loads about 9.8k tokens when it runs. Until then it costs about 78 tokens; SKILL.md has 4,433 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from mendixlabs/mxcli at commit a924d11, republished under its Apache-2.0 licence (© mendixlabs). 4,433 words, ~9,809 tokens.
.claude/skills/migrate-design-prototype/SKILL.md (or your agent's skills folder).Use this skill when you are given a Claude Design prototype / design handoff (an HTML/CSS
prototype, a *.dc.html design-console export, a tokens file, a PRD, and/or screenshots) and need to reproduce that look in a Mendix app using mxcli + MDL.
It covers the two halves of the job:
theme/web/main.scss.Class: / DynamicClasses: on create page / alter page).Related skills: atlas-design (read first — the Atlas-first taste + workflow layer),
theme-styling (SCSS compilation chain, hot-reload, styling caveats),
create-page (widget syntax), alter-page (in-place widget edits),
bulk-widget-updates (apply a class across many widgets).
Claude Design handoff Mendix app
───────────────────── ──────────────────────────────
*.dc.html / prototype ──① theme ──► mxcli theme create --from <file>
tokens / CSS / PRD create → a theme the project owns
│
component styles ──② rebuild ──► Atlas block / utility class, and only
(cards, chips, …) as classes then .ss-* classes in main.scss
│
screenshots ──③ reference ──► widgets get Class: / DynamicClasses:
(per screen) per screen via create page / alter page
│
──④ build ──► docker build → docker reload --css
│
──⑤ verify ──► compare running screen to screenshotGolden rule: the prototype is the source of truth. Before building or polishing any screen, open the matching screenshot/handoff for that screen and match it — colours, spacing, font, component shapes. Do not invent styling the prototype doesn't show.
Atlas-first (read atlas-design). Reproduce the prototype with what Atlas already
gives you before hand-writing custom SCSS. In order of preference:
use building block Atlas_Web_Content.Card / Pageheader
/ List_Cards etc. gives you the whole component's markup + styling for free. Discover
with list building blocks, inspect with describe building block.class:'card', class:'btn btn-primary',
spacing-inner-*/spacing-outer-* for padding/margin, flex-row/flex-column +
align-x-*/align-y-* for layout (no layoutgrid needed); or the typed equivalents
designproperties: ('Card style': on), ['Background color': 'Brand Primary'],
['Spacing': ['margin-bottom': 'L']]. mxcli check -p validates design-property keys
and values (MDL-WIDGET11/12) and lists the allowed values.mxcli theme create --from
(step ①). The theme maps ~60 Atlas variables onto it, so the whole app inherits
the look; hand-mapping a handful of --brand-* leaves most of Atlas on stock blue..ss-* SCSS — for brand identity only. Reach for a hand-rolled component
class (below) only when Atlas genuinely can't express the shape (bespoke chrome,
fractional-track grids, pixel-exact rows). Hand-rolling .panel/.stat/.card SCSS
that just re-implements what class:'card' already does is the single most common
mistake — see atlas-design.The rest of this skill (custom SCSS components, .ss-* classes, ListView row reshaping) is
layer 4 — the identity layer you drop to when the first three don't reach the design.
theme/web/main.scss AFTER the @imports, or in your own
partial. Styles placed after the imports win the cascade over Atlas defaults. Once
main.scss grows, prefer splitting a partial out for readability: create
theme/web/_<name>.scss and add @import "<name>"; after the Atlas imports (the same
cascade-order rule then applies within the partial). New partials are creatable —
keep the import order (custom after Atlas) and everything works..ss-panel, .ss-chip. Pick one and use it everywhere. Do not
invent a parallel set of --ss-* colour variables: the theme's --mxt-* palette is
already the app's vocabulary, and a second one silently stops following a theme or
variant swap.theme/web/custom-variables.scss holds the palette, and mxcli theme apply
writes it — it is a generated, digest-fenced block. Retune tokens there for a
one-value tweak; for a real brand, own the theme (mxcli theme create, step ①)
rather than editing inside the fence, which the next apply refuses.theme/mxcli-themes/<name>/ is where a theme the project owns lives. Committed,
and not compiled — mxbuild's entry point is theme/web/main.scss and it does
not glob theme/, so the sources sit inert until theme apply copies them into
theme/web/.theme-cache/web/ — that is the compiled build artifact.mxcli theme create --fromDo not hand-write a token block. A theme's palette is nothing but --mxt-*
custom properties, and mxcli builds one from a design artifact directly:
mxcli theme create acme -p app.mpr --from design/canvas.dc.html
mxcli theme apply acme -p app.mpr--from reads --mxt-* declarations out of any CSS-shaped text — a stylesheet,
an SCSS partial, or the <style> blocks of an HTML export — wherever they appear:
:root { --mxt-brand: #2b5170; --mxt-ground: #eef1f4; --mxt-ink: #1a2129; }
@media (prefers-color-scheme: dark) { :root { --mxt-ground: #16161a; } }Declarations inside a dark block (prefers-color-scheme: dark, .theme-dark,
[data-theme="dark"]) seed the dark palette; everything else seeds the light
one. Tokens the design does not name keep the base theme's value, so a
five-colour handoff still yields a complete, working palette.
Three reasons this beats writing the tokens yourself, and each is a mistake this skill used to teach:
--brand-primary, --topbar-bg and a handful of others leaves
most of Atlas — form controls, tables, modals, the pluggable widgets — on
stock Mendix blue.@imported from a CDN. A
@import url("https://fonts.googleapis.com/…") in main.scss is an
@import-ordering trap, a third-party request on every page load, and it
fails outright in an air-gapped deployment. The theme ships the .woff2
files under theme/web/mxcli-fonts/.:root block is one palette;
every mxcli theme carries both and follows the OS before first paint.Ask the design step to emit a --mxt-* block if you can — then this is a parse,
not a judgement call. Otherwise read the values off the prototype and write them
into a small tokens.css to pass to --from. Run mxcli theme show signal for
the full vocabulary; the ones that carry the look:
| From the handoff | Token |
|---|---|
| brand / primary, its hover, text on a brand fill | --mxt-brand, --mxt-brand-hover, --mxt-brand-ink |
| app background, cards/panels, striped rows, hover, selected | --mxt-ground, --mxt-surface, --mxt-surface-alt, --mxt-surface-hover, --mxt-surface-selected |
| body text, muted text, faint text, hairlines | --mxt-ink, --mxt-ink-muted, --mxt-ink-faint, --mxt-line |
| sidebar / topbar chrome and its text | --mxt-rail, --mxt-rail-line, --mxt-rail-ink, --mxt-rail-ink-active |
| ok / warning / danger / info | --mxt-success, --mxt-warning, --mxt-danger, --mxt-info |
| status chip fills and their text | --mxt-tint-ok / --mxt-tone-ok (and -warn, -risk, -info, -neutral) |
| body font, headings, mono, base size, line height | --mxt-font, --mxt-font-heading, --mxt-font-mono, --mxt-font-size, --mxt-line-height |
| corner radius, row/control height, elevation, focus ring | --mxt-radius, --mxt-radius-lg, --mxt-row-height, --mxt-control-height, --mxt-shadow, --mxt-focus-halo |
A --mxt-* name the base theme does not declare is refused, not written.
Nothing reads it, so the theme would apply cleanly and render unchanged — which
is indistinguishable from the design never having been applied. If a handoff
value has no token, it belongs in the theme's own skin (below), not in the
palette.
theme-styling.--font-color-default is invisible the moment the ground goes dark.The built-in themes vendor IBM Plex, Source Sans/Serif, JetBrains Mono and Space
Grotesk. For a different family, drop the .woff2 files into
theme/mxcli-themes/<name>/files/theme/web/mxcli-fonts/, add an @font-face
loop to that theme's partial beside the existing ones, and point --mxt-font at
it. Vendored, for the reasons above — not a CDN @import.
Genuinely bespoke chrome goes in the theme's own skin mixin
(@mixin mxcli-<name>-skin in theme/mxcli-themes/<name>/files/theme/web/_mxcli-<name>.scss),
where it is scoped with the theme and survives theme apply. Reach for .ss-*
classes in main.scss only for per-screen identity that is not part of the
design language — and read atlas-design first, because most of what looks
bespoke is an Atlas building block or utility class.
For each repeated element in the prototype (panel, stat tile, chip, card, table row,
progress bar…), first check whether Atlas already provides it (Atlas-first, above):
is there a building block (list building blocks) or an Atlas class / design property
(card, btn-*, spacing-*, flex-*+align-*, ['Card style': on]) that gets you
most of the way? If so, use it and add a thin .ss-* class only for the brand delta
(colour, radius, font). Re-implementing card/panel/btn from scratch is the mistake
atlas-design exists to prevent.
When Atlas can't express the shape, write one reusable class driven by the theme's
tokens. Never a literal colour: a literal survives a light/dark flip and a theme
swap, and is wrong under every palette but the one you wrote it against. Keep classes
small and composable so a widget can stack several (Class: 'ss-panel ss-grid-lv').
Check the theme first — .pill and .stat below already ship as recipe classes
(mxcli theme show <name>), so a chip and a KPI tile usually need no CSS at all.
// Surface panel — every value resolves through the palette
.ss-panel {
background: var(--mxt-surface);
border: 1px solid var(--mxt-line);
border-radius: var(--mxt-radius);
box-shadow: var(--mxt-shadow);
}
// Status chip — one base + colour modifiers. The theme's own `pill` /
// `pill-ok` / `pill-warn` / `pill-risk` do this already; write your own only
// if the design's shape genuinely differs.
.ss-chip {
display: inline-block; border-radius: 11px; padding: 2px 10px;
font-family: var(--mxt-font-mono); font-size: 11px; font-weight: 600;
border: 1px solid transparent;
white-space: nowrap; // status chips must never wrap to 2 lines
}
.ss-chip--ok { background: var(--mxt-tint-ok); color: var(--mxt-tone-ok); }
.ss-chip--danger { background: var(--mxt-tint-risk); color: var(--mxt-tone-risk); }Base + modifier convention. Give each component a base class and add --variant
modifiers for state/colour (.ss-chip + .ss-chip--danger, .ss-heat--ok/--warn/--over).
Widgets then combine base + modifier: Class: 'ss-chip ss-chip--danger'.
Reshaping Mendix chrome. To make Atlas widgets read like the prototype you often need to override Mendix's own DOM classes. Common targets:
Sidebar / topbar shell: .region-topbar, .mx-header, .region-sidebar,
.mx-scrollcontainer-left, and nav items under .mx-navigationtree.
ListView rows are the workhorse for grids/tables — neutralise Atlas's default row chrome (padding/border/background) so rows read as your design's grid lines:
.ss-grid-lv > ul > li,
.ss-grid-lv .mx-listview-item {
padding: 12px 16px !important;
margin: 0 !important;
border-bottom: 1px solid var(--mxt-line);
}::before / ::after on .mx-navigationtree can inject brand blocks / section labels
the design shows but the Mendix nav model doesn't produce.
Use !important sparingly but expect to need it when overriding Atlas widget CSS.
The lookup that removes the guesswork: for each component in the prototype, pick the widget
here first, then style it with your --<prefix> classes. Validated across the BAE Resource
Scheduling and Expense Approval designs.
| Design component | Mendix widget | Notes |
|---|---|---|
| Page / screen canvas | container | one per page, e.g. Class: 'ea-page' |
| Card / panel / section | Atlas Card building block, or container class:'card' / ['Card style': on] | drop to a custom panel class only for brand delta |
| KPI / stat tile | container | label + value + delta as child dynamictext |
| Row/column layout (even columns, gaps) | container with flex-row/flex-column + align-* (or ['Flex container': …]) | no layoutgrid needed for simple flex layouts |
| Multi-column / dashboard layout | layoutgrid + row + column | for exact fractional tracks (2.4fr 1.2fr …) use a container styled display:grid instead — see Layout techniques |
| Heading / title | dynamictext (RenderMode H1/H2) | |
| Body / label / caption / table cell | dynamictext | the workhorse — text is inline, see techniques |
| Metric / big number | dynamictext | mono class |
| Chip / badge / tag / status pill | dynamictext | base class + colour modifier; leading dot via CSS ::before |
| Data table / grid / row list | listview (database source; row = layoutgrid or grid container) | preferred for bespoke row layouts — full control of the row markup; the datagrid pluggable widget exists but is heavier to style to a custom design |
| Table header row | static header band (container/layoutgrid) above the listview | |
| Tabs / segmented / filter-chip row | tabcontainer styled as pills (one tabpage per XPath-filtered view) | for static/decorative chips use dynamictext |
| Master list + detail pane | listview (Selection) + dataview (DataSource: SELECTION) | |
| Detail / read view | dataview | |
| Create / edit form | dataview + inputs + footer | Save/Cancel in the footer |
| Text input / multiline | textbox / textarea | |
| Dropdown / enum select | combobox | bound to an enum or association |
| Date field | datepicker | |
| Boolean / toggle | checkbox | |
| Button (primary/secondary) | actionbutton | ButtonStyle or a class |
| Link / text button | linkbutton | |
| Search box | listview built-in search bar | hoist/restyle via CSS |
| Avatar / initials | dynamictext | styled as a circle |
| Image / logo / thumbnail | image / dynamicimage / staticimage | |
| Icon / colour dot | CSS ::before on a class | |
| Chart (line/bar/column/pie/area/bubble) | chart pluggable widget (Mendix Charts / ChartJS) | via PLUGGABLEWIDGET '<id>' — see Pluggable widgets below; needs a datasource + series config |
| Donut / gauge | ProgressCircle pluggable widget | via PLUGGABLEWIDGET '<id>'; static or attribute-driven — worked example below |
| Progress bar / meter | progressbar widget, or a container (track + fill) | a styled track+fill container needs no widget package |
| Sparkline / bespoke SVG | HTMLElement pluggable widget, or a container with a CSS SVG background | embed the design's inline SVG directly |
layoutgrid is a 12-column system and can't express
ratios like 2.4fr 1.2fr 1fr 1.4fr 0.6fr. For pixel-faithful tables/dashboards, style a plain
container as display:grid; grid-template-columns: … in its class and put the cell widgets
as its direct children — each widget becomes a grid item.dynamictext is inline by default. For stacked text (a title over a subtitle) set
display:block in the class, or the lines run together.Pluggable widgets do round-trip through MDL — but not by bare name. A bare
progresscircle / CUSTOMWIDGET is rejected by the builder ("unsupported widget
type"). The working form uses the widget's full package id as a quoted string:
PLUGGABLEWIDGET '<widget.package.id>' widgetName ( prop: value, … ) { childslots }One-time registration. The widget package must be present in the project's widgets/
before you can reference its id:
mxcli widget init -p baedemo.mpr # scaffold pluggable-widget support (run once)
mxcli widget extract -p baedemo.mpr --mpk widgets/ProgressCircle.mpk # register a package
mxcli widget list -p baedemo.mpr # list available widget ids + their propsmxcli widget list prints each widget's id and property names — copy the id verbatim into
the PLUGGABLEWIDGET '…' string, and use the property names it reports as the widget's props.
Worked example — the status donut (Expense Dashboard). A ProgressCircle in static mode,
with a text label overlaid via a sibling container (the widget draws only the ring):
container donutWrap (Class: 'ea-donut') {
PLUGGABLEWIDGET 'com.mendix.widget.custom.progresscircle.ProgressCircle' donut (
type: 'static', staticCurrentValue: 67, staticMinValue: 0, staticMaxValue: 100, showLabel: false
) { }
container donutLabel (Class: 'ea-donut-label') {
dynamictext donutPct (Content: '67%', Class: 'ea-donut-pct')
}
}This passed mx check with 0 errors, survived docker build, and renders its SVG arc at
runtime (verified on the dashboard).
Real Mendix Charts (BarChart/LineChart/PieChart/HeatMap/…) are fully authorable
too — each series/line binds its own OQL-view datasource + X/Y attributes;
Pie/HeatMap bind at the widget level (ValueAttribute:, Pie needs SeriesName:).
See Custom & Pluggable Widgets → Charts for the chart-type
→ id table, per-chart required-property gotchas (TimeSeries needs a datetime X,
Bubble needs a size attribute), and the CE0463 → mxcli fix widgets step
(it normalizes the stored widgets and preserves MPRv2 storage — never run bare
mx update-widgets on a mxcli new project; it deletes mprcontents/).
mdl-examples/doctype-tests/34-chart-widget-examples.mdl is the full showcase.
Pluggable-widget gotchas:
listview, dynamictext, container, gallery, combobox
need no registration. Drop to a pluggable widget only when the design genuinely needs one
(charts, gauges, embedded SVG, maps, sliders). Many "charts" in a handoff are just static
SVG — a container with a CSS background SVG (KPI sparklines, area trends here) is lighter
than a real chart widget and needs no datasource.activity, legend, etc. are rejected by the
parser; rename (actCard, legendCol).{ }. Always close the child-slot braces, even when empty.Most Claude Design prototypes render a persistent sidebar + topbar on every screen — a brand block, a menu, sometimes a footer tag. It is tempting to rebuild that chrome inside each page. Don't. In Mendix the shell is not a page — it comes from two shared places:
Atlas_Core.Atlas_Default in this project) provides the topbar + left
sidebar regions. Every page sets Layout: Atlas_Core.Atlas_Default, so they all inherit the
same shell; the page's own widgets render only in the content region.Responsive profile drives the whole
app — home page, login page, and the flat/nested menu. Menu items point at pages, not at
widgets you place.So the prototype's sidebar maps to navigation config + layout styling, configured once, and its menu grows by adding navigation items — never by editing pages.
mxcli -p baedemo.mpr -c "LIST NAVIGATION" # profiles, home page, item count
mxcli -p baedemo.mpr -c "LIST NAVIGATION MENU Responsive" # the menu treeAdd or reorder items with CREATE OR MODIFY NAVIGATION <Profile> … (full-replacement — dump
the current profile first with DESCRIBE NAVIGATION <Profile>, edit, re-apply). See
manage-navigation for the item syntax, home/login pages, and role-based homes.
The menu items and regions are standard Atlas DOM, so the prototype's look is reproduced with
CSS in main.scss (step ②) — you do not model the sidebar's chrome as widgets:
--sidebar-bg, --topbar-bg, --navigation-bg)
or by overriding .region-sidebar / .region-topbar / .mx-header directly..mx-navigationtree (idle / hover / active states, spacing, the
active-item accent bar).WORKSPACE section
label, an ITERATION 1 · DEMO footer tag — with ::before / ::after on .mx-navigationtree
(or the sidebar region). The Mendix navigation model has no field for these, so CSS
pseudo-elements are the right tool; keep their text in the SCSS with the rest of the theme.Rule of thumb: if a design element is the same on every screen, it belongs to the shell (navigation + layout + CSS), not to a page. Only the content region is built per-page in ③.
Recolouring is rarely enough — most prototypes put a full-height sidebar (brand block at the
very top) with the topbar only over the content, whereas Atlas_Default renders the topbar
full-width above a sidebar+content row. Reproduce the prototype layout with CSS, no custom
layout document needed:
.mx-scrollcontainer-left.region-sidebar { position: fixed; top: 0; left: 0; height: 100vh; z-index: 100; }
.region-topbar, .mx-scrollcontainer-top,
.region-content, .mx-scrollcontainer-center { margin-left: 256px !important; } /* = sidebar width */flex-basis, not width/height. Atlas sizes the topbar and
sidebar as flex items, so plain width/height is ignored — use
flex: 0 0 256px !important (sidebar) and flex: 0 0 62px !important (topbar)..toggle-btn { display: none !important; }..mx-scrollcontainer-wrapper) should be flex: 1 1 auto so a
::after footer tag pins to the bottom.A ::before/::after pseudo-element is a single box with one text style — it cannot render a
brand block (logo square + title + differently-styled subtitle) or a user chip (avatar circle +
two text lines) faithfully. For those, render the whole element as an inline-SVG background
image, which gives you exact sub-shapes, fonts, and colours:
.region-sidebar::before {
content: ''; height: 80px; border-bottom: 1px solid rgba(255,255,255,.07);
background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='256' height='80'>\
<defs><linearGradient id='g' x1='0' y1='0' x2='1' y2='1'><stop offset='0' stop-color='%233a6fd6'/>\
<stop offset='1' stop-color='%231d3f86'/></linearGradient></defs>\
<rect x='22' y='21' width='38' height='38' rx='10' fill='url(%23g)'/>\
<text x='41' y='45' fill='%23fff' font-family='Public Sans' font-size='15' font-weight='800' text-anchor='middle'>EA</text>\
<text x='72' y='37' fill='%23fff' font-family='Public Sans' font-size='15' font-weight='700'>Expense Approval</text>\
<text x='72' y='55' fill='%237d8ba4' font-family='IBM Plex Mono' font-size='11'>Finance workflow</text></svg>") no-repeat;
}Encode # as %23 in the data URI. Use the same trick for the topbar's decorative search field
(a ::before box with a magnifier-icon SVG background) and the user chip (::after). Simpler
single-style chrome — the WORKSPACE label, the PROTOTYPE · DEMO footer, per-item nav dots
(.mx-navigationtree a::before { content:''; width:9px; height:9px; border-radius:3px; background: … },
coloured per item by its .mx-name-* anchor class) — stay as plain pseudo-elements.
Decorative-only: an injected search box / avatar / "New" button is not interactive (it's CSS). The prototype's are usually mockups too; if the design needs a working search or profile menu, that's a real widget in a custom layout (Studio Pro), not CSS on the Atlas shell.
Every Mendix widget takes a Class: (static) and DynamicClasses: (expression) property.
This is how the theme reaches the page. Prefer native Mendix widgets styled with your
classes — container, listview, dataview, dynamictext, tabcontainer — over custom
widgets, which are far harder to drive from MDL.
Space-join base + modifiers in a single Class: string:
mdl 1;
create or modify page ResourceScheduling.ResourceHeatmap (
Title: 'Resource Heatmap', Layout: Atlas_Core.Atlas_Default
) {
container heatmapPage (Class: 'ss-page') {
dynamictext heatmapTitle (Content: 'Resource Heatmap', RenderMode: H1, Class: 'ss-page-title')
listview loadLV (
DataSource: database from ResourceScheduling.Resource where LoadSeries != empty,
Class: 'ss-panel ss-heat-lv'
) {
container heatRow (Class: 'ss-heat-row') {
dynamictext hc01 (Content: '{1}', ContentParams: ({1} = M01), Class: 'ss-heat-cell')
}
}
}
};DynamicClasses: expressionFor colour/state that depends on data (over-capacity cell, conflict card, load bucket),
use a DynamicClasses: expression returning a space-separated class string. It stacks on
top of Class:.
container heatCell (
Class: 'ss-heat-cell',
DynamicClasses: if $currentObject/M01 >= 100 then 'ss-heat--over'
else if $currentObject/M01 >= 80 then 'ss-heat--warn'
else 'ss-heat--ok'
)(Written as-is: plain single quotes inside, no outer quotes around the expression.)
A widget has no computed inline style: Style: is a static string and
DynamicClasses: returns class names, not CSS values. So anything with a
data-driven dimension — a progress bar width, a bar-chart height, a meter fill —
can't be Style: 'width: {expr}%'. The idiom is to quantise the value into a
bucket and generate one class per bucket:
0..20):
$obj/PctBucket = round($obj/Done / $obj/Total * 20).@for loop:@for $i from 0 through 20 { .ss-pb-#{$i} { width: $i * 5%; } }DynamicClasses: 'ss-pb-' + toString($currentObject/PctBucket).Trade-off worth noting: this adds one bucket attribute per animated dimension to the domain model. Pick a bucket count that matches the visual precision you need (20 → 5% steps is usually plenty).
Use alter page to attach a class without rewriting the page (see alter-page):
mdl 1;
alter page ResourceScheduling.Approvals {
set (Class: 'ss-appr-card ss-appr-card--conflict') on queueCard;
};To apply the same class across many widgets/pages at once, see bulk-widget-updates
(update widgets ... dry run first).
SCSS is not live — you must compile before the theme shows. See theme-styling.
mxcli docker build -p baedemo.mpr # compiles SCSS into the deployment package (~55s)
mxcli docker reload -p baedemo.mpr --css # pushes compiled CSS to browsers (instant)--css only pushes already-compiled CSS — always docker build first after editing SCSS.Class: on a page), use a normal docker reload.Put the running screen next to the handoff screenshot and reconcile the diff — spacing,
colours, font, component shapes. The project already has Playwright wired in (test-app)
for screenshotting the running app. Iterate ②–④ per screen until it matches.
A layout that should be two columns comes out as one, and the CSS is right — it
is on the wrong element. Mendix wraps a repeating widget's children in an
intermediate element, so display: grid on the widget's own class has exactly
one grid item and every card stacks:
div.mx-listview.my-cards [689x2054] display=grid <- the class you wrote
ul. [334x2038] display=block <- ONE child
li.mx-name-index-0 [334x526] <- the things you meant to lay outA data view does the same with .mx-dataview-content. One project hit this
twice in two different widgets before naming the rule (mxcli-owid, findings #15
and #41).
Put the grid on the element that actually holds the repeated children:
/* list view */
.my-cards > ul { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.my-cards > ul > li { min-width: 0; }
/* data view */
.my-page > .mx-dataview-content { display: grid; grid-template-columns: 240px 1fr; }min-width: 0 on the child matters: a grid item defaults to min-width: auto,
so a wide table or a long unbroken string inside a card pushes the column past
its track instead of scrolling within it.
How to find the right element rather than guess: run the app, inspect the
widget, and walk down from the class you wrote until you reach the element with
one child per row. mxcli run --local --screenshot plus the browser inspector
settles it in one pass — see .claude/skills/verify-in-runtime.md.
Style: (inline style) on a DYNAMICTEXT — it crashes MxBuild with a
NullReferenceException. Class: on a DYNAMICTEXT is fine; for inline style, wrap it in a
styled container instead. (Same applies to alter styling/alter page set style.)Class: + a real CSS class over inline Style:. It keeps the design system in
one place and dodges the DYNAMICTEXT crash.DynamicClasses: for state, not duplicate widgets. One widget + an expression beats
cloning a widget per state.white-space: nowrap so labels like "Fully allocated" never wrap..ss-*-lv class or rows won't read as the design's grid.listview over the datagrid pluggable widget —
you control the full row markup, which a pixel-faithful design usually needs.designproperties: / Atlas classes over custom SCSS for anything Atlas
covers (spacing, alignment, card/background, flex layout) — see atlas-design. Keys
and values are case-sensitive; mxcli check -p validates them (MDL-WIDGET11/12) and lists
the allowed values. theme-styling has the compilation/reload mechanics.alter styling can't find widgets in MDL-builder-created pages — apply classes via
Class:/DynamicClasses: in create page / alter page instead.Distilled from a full prototype build. Some are covered in depth by the linked skills — this is the fast index so a design migration doesn't rediscover them.
docker build is the only trustworthy check. mxcli check --references passes MDL
that mxbuild rejects. Build after every slice before you screenshot.create entity, attribute: bindings, and create/change targets when they
collide with an MDL keyword. Inside expressions — microflow if/decisions, contentparams,
visible, dynamicclasses, and inline-bracket XPath where — mxcli now strips stray
identifier quotes, so a quoted attribute no longer breaks the build or makes an XPath where
silently return 0 rows; still prefer the unquoted form there for readability. See
write-microflows.dataview dvEmp (datasource: $currentObject/Module.Entity_Related) {
dynamictext n (content: 'By {1}', contentparams: ({1} = Name)) -- own attr of the related entity
}contentparams: ({1} = Entity_Related/Name)
in a dynamictext, or attribute: Entity_Related/Name on a DataGrid2 column — both persist as
an AttributeRef over the association (use a bare association name; a module-qualified one is
rejected on a column).dynamictext + a DynamicClasses: expression mapping the enum/value to
a colour modifier (ea-chip--ok/--warn/--danger). Base .ea-chip + modifiers; white-space: nowrap.ProgressCircle / ProgressBar build and work (donut, meters). Mendix Charts
(Charts.mpk: column/bar/line/area/pie) now author via MDL — each series (an object-list
item inside the chart) binds a datasource plus X/Y attributes:
series s1 (staticDataSource: database from Module.View, staticXAttribute: "X", staticYAttribute: "Y")
(a per-series OQL view works too). mxcli fix widgets clears the
widget-version-drift CE0463 (it normalizes the stored widgets and preserves MPRv2 storage —
do not run bare mx update-widgets, which deletes mprcontents/). Still lighter when the design allows: a CSS-background
SVG container (or HTMLElement) for sparklines/trends — no datasource — and ProgressCircle
(type: expression, expressionCurrentValue: '$currentObject/Rate', min '0' / max '100',
labelType: percentage) for a single-value gauge.write-oql-queries). A grouped enum column in
the view must be typed enumeration(Module.Enum), not string, or you get CE6770. Test with
mxcli oql first.return true. Direct-SQL seeding doesn't apply to the default HSQLDB. See demo-data.docker up --detach --wait) to
register — a hot reload won't see them. Hot reload also occasionally crashes the runtime; if
the app stops responding, docker up --detach --wait recovers it with data intact.mxcli version
and confirm with describe page.mxcli theme create --from <design-file>, not hand-written — the Atlas mapping is ~60 variables and the theme already does it--mxt-* token uses it; anything left over went into the theme's skin mixin, not the palettemxcli theme apply <name> -p app.mpr run, and the theme verified in a browser (light and dark)theme/web/mxcli-fonts/ — no @import url("https://fonts.googleapis.com/…") anywhere--variant modifiers, all using the project prefixCREATE OR REPLACE NAVIGATIONposition:fixed + margin-left offset; region sizes forced with flex-basis; collapse toggle hidden if unused)::before/::aftercontentparams: ({1} = Assoc/Attr) / column attribute: Assoc/Attr) for a single inline value — both persistdocker build run after each slice (not just mxcli check) before screenshottingClass:; data-driven state via DynamicClasses:Style: on any DYNAMICTEXTlistviews, not custom Datagrid widgetsPLUGGABLEWIDGET '<id>' … with the package registered via mxcli widget extract; static SVG (sparklines) done as CSS-background containersdocker build then docker reload --css after SCSS edits© mendixlabs, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .claude/skills/mendix/migrate-design-prototype of mendixlabs/mxcli.
Open the folder on GitHubat commit a924d11
Migrate Design Prototype next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Migrate Design Prototype this skillmendixlabs/mxcli | 128 | — | ~9.8k | Automated safety check: Pass | Apache-2.0 | |
| Component Refactoringlangflow-ai/langflow | 156k | — | ~3.5k | Automated safety check: Pass | MIT | |
| @pierre/diffs Code Renderingpierrecomputer/pierre | 6.2k | 2 repos | ~803 | Automated safety check: Pass | Apache-2.0 | |
| Frontend Code Reviewlanggenius/dify | 158k | — | ~938 | Automated safety check: Pass | Custom licence | |
| Electron DevTools Trace Analysiskeybase/client | 9.3k | — | ~809 | Automated safety check: Pass | BSD-3-Clause | |
| Moonbit Docs Maintainermoonbitlang/moonbit-docs | 2.4k | — | ~1.1k | Automated safety check: Pass | Custom licence |
langflow-ai/langflow
Refactor high-complexity React components in Langflow frontend.
pierrecomputer/pierre
Guides an agent through using @pierre/diffs to render syntax-highlighted files and diffs, and to build editing and review surfaces in React or plain JavaScript.
langgenius/dify
Reviews frontend changes under `web/` or `packages/dify-ui/` for concrete defects and broken project contracts, using routed rule packs and a severity scale for findings.
keybase/client
Analyzes Chrome or Electron DevTools Performance trace exports with Python scripts to find where render time actually goes, without opening DevTools.
moonbitlang/moonbit-docs
A skill your agent uses when maintaining the moonbitlang/moonbit-docs repository, including Sphinx docs under next/, MoonBit examples under next/sources/, error-code documentation, gettext…
smallnest/pigo
Create professional, dark-themed architecture diagrams as standalone HTML files with SVG graphics.
mendixlabs/mxcli
Push OData query options into the SQL of a Mendix resource served by a read microflow, so $filter, $orderby, $top, $skip, $count and the key lookup reach the database instead of being silently…
mendixlabs/mxcli
Chart a Mendix app with Vega-Lite through a pluggable widget that takes the specification and the data as separate properties, so the model emits rows and never assembles a chart payload.
mendixlabs/mxcli
Author Mendix AI agent documents in MDL — Model, Knowledge Base, Consumed MCP Service and Agent, with variables, tools and multi-line prompts.
mendixlabs/mxcli
Run set-based INSERT, UPDATE and DELETE against Mendix entities through OQL statements, which the runtime supports and Studio Pro cannot author.
mendixlabs/mxcli
Stand up an HTTP endpoint you control instead of a live third-party API, and point the Mendix app at it — Prism from an OpenAPI contract, a constant swap, or a forward proxy.
mendixlabs/mxcli
Call external REST APIs from Mendix — the three approaches (inline REST CALL, consumed REST client document, generated from OpenAPI) and how to choose.
Categories
Reproduce a Claude Design prototype or design handoff (HTML/CSS, .dc.html export, tokens, screenshots) inside a Mendix app: build the palette with mxcli theme create --from, then apply classes in…. Migrate Design Prototype is an agent skill from mendixlabs/mxcli.html export, tokens, screenshots) inside a Mendix app: build the palette with mxcli theme create --from, then apply classes in pages with MDL.
Migrate Design Prototype fits situations like: given a design artefact and asked to make the app look like it.
Run `npx skills add mendixlabs/mxcli --skill migrate-design-prototype -a claude-code`. Or copy the skill folder (.claude/skills/mendix/migrate-design-prototype in mendixlabs/mxcli) into .claude/skills/migrate-design-prototype in your project. Claude Code loads it when a task matches its description.
Run `npx skills add mendixlabs/mxcli --skill migrate-design-prototype -a codex`. Or copy the skill folder (.claude/skills/mendix/migrate-design-prototype in mendixlabs/mxcli) into .agents/skills/migrate-design-prototype in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add mendixlabs/mxcli --skill migrate-design-prototype -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/migrate-design-prototype, .gemini/skills/migrate-design-prototype, .github/skills/migrate-design-prototype and .opencode/skills/migrate-design-prototype in your project.
Going by SKILL.md and its folder, Migrate Design Prototype needs the command-line tools its instructions call (docker and claude). Our summary lists: Docker.
SKILL.md names 2 domains. In commands or code: fonts.googleapis.com and w3.org; the agent is likely to contact these when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Migrate Design Prototype is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 9.8k tokens (SKILL.md is roughly 39k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Migrate Design Prototype: Component Refactoring (langflow-ai/langflow, 156k stars), @pierre/diffs Code Rendering (pierrecomputer/pierre, 6.2k stars), Frontend Code Review (langgenius/dify, 158k stars) and Electron DevTools Trace Analysis (keybase/client, 9.3k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
mendixlabs (a GitHub organization) maintains it in mendixlabs/mxcli, which has 128 GitHub stars. The repository holds 75 skills in this directory. The repository was last updated on October 7, 2026.
Source: mendixlabs/mxcli on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.