Migration
Move from CCL, MUI, LuiThemeProvider, and JDA icon libraries to @by/experience-system, @by/icons, and ThemeProvider—principles, strategy, styling, Migration CLI Tools (`by-es`), scan, and resources.
This guide is for teams replacing or narrowing CCL and Material UI with @by/experience-system, @by/icons, Tailwind-first styling, and optionally @by-es.
| Doc | Use it when… |
|---|---|
| Getting started | Adoption path, Tailwind, ThemeProvider, registry |
| Installation | Exact install commands, @source, peers |
| ESLint Plugin | Continuous migration and composition feedback |
| Theming | Tokens, ThemeProvider, density |
What migration means here
You are not swapping imports while keeping the same theme JSON in memory. You are changing where visual truth lives: from JS theme objects in React context to DOM attributes (data-ds-theme, data-ds-color, data-ds-spacing, dir) plus theme.css and Tailwind variants.
| Topic | Legacy (typical) | Target |
|---|---|---|
| Theme | MUI / LUI ThemeProvider, createTheme, sx, styled, makeStyles, LuiThemeProvider | ThemeProvider from @by/experience-system + tokens (theme.css) |
| Icons | @jda/lui-common-icon-library-mui5 | @by/icons (paired with @by/experience-system) |
| Portal | Theme hints from shell (historically MUI-shaped) | Bridge into ThemeProvider — see Portal theme consumption |
Expect three kinds of churn together:
| Churn type | What helps |
|---|---|
| Imports | by-es migrate scan (CLI) |
| Styling | Styling below + Theming |
| Composition | Getting started, primitives vs registry blocks |
Principles
| Principle | In practice |
|---|---|
| Accessibility & behavior | Focus rings, keyboard paths, ARIA are part of the component contract—you compose Dialog, triggers, etc., rather than one opaque widget per variation (Radix-style). |
| Composition | Prefer small primitives (Field, Label, Button) + registry blocks your platform owns over indefinite MUI wrappers. |
| Reviewable styling | Tailwind + semantic tokens make diffs easier than giant sx / styled() blobs (Theming). |
| Bundle surface | Named imports from @by/experience-system — shrink accidental MUI / CCL subgraphs as you migrate screens. |
Strategy
| Tactic | Why |
|---|---|
| Vertical slices | Stand up ThemeProvider, theme.css, @source per route/MFE—avoid a long-lived “migration branch” that blocks main. |
| Clear boundaries | Wrap new work with ThemeProvider at router/MFE edges; legacy keeps LuiThemeProvider / MUI ThemeProvider until touched. |
| Dependency hygiene | Confirm React, Tailwind, MUI combinations your org still supports alongside @by/experience-system peers — Installation. |
| Portal / shell | Bridge themeMessage.themeObject into ThemeProvider — Portal theme consumption, portal-theme-message-skill. |
| Parallel stacks | Expect two styling dialects for a while; make differences explicit — setup-tailwind-theme-provider-skill on Skills. |
Styling
Scope: mental models for appearance and layout—not every API name. The Button example below illustrates patterns that apply across typography, surfaces, inputs, and layout.
| MUI / CCL (leaving) | BY (moving toward) | |
|---|---|---|
| Where look & feel live | Theme object: palette, theme.spacing, typography, sx / styled / makeStyles | CSS: variables in theme.css, utilities, Button variant, ThemeProvider attributes |
| Dark / density | Often separate theme objects or flags | dark:, compact:, variants tied to ThemeProvider |
| Review phrase | “This sx uses theme.spacing(2)…” | “This uses gap-scaled-4 + text-neutral-12 under ThemeProvider color=dark” |
Note: Enabling Tailwind runs preflight — legacy markup may shift (heading margins, lists, controls). BY assumes preflight on. Disabling it to “fix” legacy pages usually breaks new surfaces—plan a visual pass the first time Tailwind loads.
Example: MUI styled components to BY Button
Before
import { styled } from '@mui/material/styles';
import Button from '@mui/material/Button';
const CustomButton = styled(Button)(({ theme }) => ({
backgroundColor: theme.palette.primary.main,
color: 'white',
padding: '8px 16px',
borderRadius: '4px',
'&:hover': {
backgroundColor: theme.palette.primary.dark,
},
margin: theme.spacing(1),
}));
export const MyComponent = () => {
return <CustomButton variant="contained">Submit</CustomButton>;
};After
import { Button } from '@by/experience-system';
export const MyComponent = () => {
return (
<div className="m-4">
<Button type="button" variant="fill">
Submit
</Button>
</div>
);
};Prefer semantic colors from Theming and Semantic colors over hard-coded palette utilities.
During transition: Passing className into MUI you still own is common—that differs from ad-hoc utilities on @by/experience-system atoms; stick to documented props and variants on BY primitives.
Migration CLI Tools
The by-es binary ships with @by/experience-system. Commands:
Note: Use the Experience System ESLint plugin alongside the migration CLI. The CLI inventories and transforms existing legacy usage, while lint rules keep new migration, composition, accessibility, and optional style issues from entering changed code.
| Command | Status |
|---|---|
migrate scan | Supported — inventory legacy imports, summary aggregates, and per-import findings (see Scan below). |
migrate report | Supported — summary aggregates only (planning rollups, no per-import table) — from a scan or saved scan JSON (see Report below). |
migrate codemod | Supported for conservative direct replacements — dry-run by default, writes with -w (see Codemod below). |
Usage
Pick one pattern depending on whether @by/experience-system is already in package.json.
From the project dependency
Use pnpm exec / npm exec / yarn exec so CI resolves the by-es binary from node_modules / your lockfile:
pnpm exec by-es migrate scan
npm exec -- by-es migrate scan
yarn exec by-es migrate scannpx by-es may resolve locally when the package is installed; for CI, prefer exec for deterministic paths.
Using npx
One-off audit without adding the dependency:
npx @by/experience-system by-es migrate scanOptional version pin:
npx @by/experience-system@latest by-es migrate scan --dir ./src --format jsonDependency scan
Purpose: Find .ts, .tsx, .js, .jsx imports from legacy stacks; print migration hints where maintainers defined them. Icons should move to @by/icons—empty hints still show where legacy code lives. By default, results go to docs/migration_scan.<ext>; --stdout prints only to the terminal.
Targets
--target | Package universe |
|---|---|
v4 (default) | v3 — CCL v3 / MUI v5 list below |
v3 | v2 — CCL v2 / MUI v4 list below |
v2 — CCL v2 / MUI v4
@jda/lui-common-component-library@material-ui/core@material-ui/icons@material-ui/lab@material-ui/pickers@material-ui/styles@material-ui/system@material-ui/types@material-ui/utils
v3 — CCL v3 / MUI v5
@jda/lui-common-component-library-mui5@jda/lui-common-icon-library-mui5→ replace with@by/icons@mui/icons-material@mui/lab@mui/material@mui/x-date-pickers@mui/styles@mui/system@mui/types@mui/utils
| Flag | Purpose |
|---|---|
--dir, -D | Directory to scan (default: cwd). |
--gitignore, -G | Path to .gitignore (default: .gitignore). |
--verbose, -V | Verbose logging. |
--format, -f, -O | table (default), json, or markdown. |
--stdout | Print to the terminal only; do not write a file. |
--out, -o | Explicit output file path (relative to cwd); overrides docs/migration_scan.<ext>. |
--out-dir | Folder for the default migration_scan filename (default docs). |
--name | Default basename without extension (default migration_scan). |
--target, -T | v3 or v4. |
--top-files / --top-folders | Cap hotspot listings in summary (default 20 each). |
--help, -h | Help. |
pnpm exec by-es migrate scan
pnpm exec by-es migrate scan --dir ./src --format json
pnpm exec by-es migrate scan --stdout
pnpm exec by-es migrate scan --format markdown --out ./migration_audit.md
npx @by/experience-system by-es migrate scan --target v3Output
| Format | Typical file | Best for |
|---|---|---|
| Table | docs/migration_scan.txt | Plain-text summary plus ASCII tables grouped by module in the findings. |
json | docs/migration_scan.json | Tickets, dashboards, scripting; summary + findings tree. |
Markdown | docs/migration_scan.md | Readable summary plus Markdown tables (rollup plus per-import tables). |
Scan JSON schema (version 2.0)
by-es migrate scan --format json writes a deterministic JSON tree with meta, rollup summary, and per-import findings (stable sort).
| Property | Description |
|---|---|
summary | Aggregates for planning (totals, byPackage / category / confidence counts, hotspots, links/registry/skills, warnings) — identical to migrate report JSON/Markdown (MigrateReportResult). |
meta | schemaVersion is 2.0; target (v3 | v4) matches --target; scanRoot is the absolute --dir. |
findings | Ordered per-import rows (see findings[] below). |
Meta object fields
| Field | Description |
|---|---|
meta.schemaVersion | 2.0 for this contract; bump when the shape changes. |
meta.target | v3 or v4 scan preset (matches --target). |
meta.scanRoot | Absolute path passed via --dir. |
Findings entries
| Field | Description |
|---|---|
findings[].filePath | Path relative to scanRoot. |
findings[].line / column | 1-based position (UTF-16 columns per TypeScript). |
findings[].position | filePath:line:column for quick lookup. |
findings[].importKind | named, default, or namespace. |
findings[].isTypeOnly | true for import type or type-only named bindings. |
findings[].importedSymbol | Exported name (Button, default, \* for namespace). |
findings[].localName | Local binding (includes Foo as Bar aliases). |
findings[].migrationAction | Hint from structured migration matrix (each row exposes message; scan JSON still emits this flattened string plus category/docs fields). @mui/ / @material-ui/ imports with no symbol-specific row fall back to a default message linking /system/overview/migration. |
findings[].migrationCategory | Structured matrix bucket (direct-replacement, registry-recipe, compose-from-primitives, theme-token, icon-migration, styling-migration, theme-provider-migration, manual-review, unknown). |
findings[].confidence | high | medium | low | unknown heuristic from guide text. |
findings[].suggestedReplacement | Optional mirror of actionable hints when present. |
findings[].relatedDocs | Design-site paths under /system/... when suggested. |
findings[].registry / skills | @by-es registry item names / skill ids when referenced (may be empty). |
Older tooling that expected legacy JSON keyed only by module should migrate to this shape (schemaVersion marks the cut).
Still on MUI v4 → v5? Follow Material UI migration and mui-replace codemod before assuming every hit maps to @by/experience-system.
Migration report
Purpose: Emit summary aggregates only (MigrateReportResult) — the same planning rollups as the summary object in migrate scan, without per-import findings. Runs a fresh scan (--dir, --gitignore, --target, --verbose same as scan) or --from-scan-json (reads meta + findings; any embedded summary is ignored and recomputed).
| Flag | Purpose |
|---|---|
--output, -O | markdown (default) or json (summary JSON only). |
--from-scan-json | Path to migrate scan JSON; skip walking the tree. Match --target to the scan for correct package rollups. |
--stdout | Print to the terminal only — do not write a file. |
--out, -o / --out-dir / --name | Default docs/migration_report.md or docs/migration_report.json by format (same pattern as scan). |
--top-files / --top-folders | Caps for hotspot lists in the summary (default 20 each). |
pnpm exec by-es migrate report --dir ./src
pnpm exec by-es migrate report --output json --stdout
pnpm exec by-es migrate report --from-scan-json ./docs/migration_scan.json --out ./migration-rollup.mdCodemod
Purpose: Apply conservative, component-level mechanical edits for the v4 migration target. It is a direct-replacement codemod, not a full migration engine: it changes imports, JSX tag identifiers, approved static prop names/values, and exact-match legacy LUI icon imports.
The command is dry-run by default. Add -w / --write to update files.
pnpm exec by-es migrate codemod --dir ./src --stdout
pnpm exec by-es migrate codemod --dir ./src --format json --out docs/migration_codemod.json
pnpm exec by-es migrate codemod --dir ./src -w
npx @by/experience-system by-es migrate codemod --dir ./src --stdout| Automated surface | What changes |
|---|---|
| Direct component replacements | Confirmed one-to-one CCL/LUI and selected @mui/material components move to @by/experience-system. Imports are removed or split when only some specifiers move. |
| JSX component identifiers | Unaliased JSX tags are renamed when the target component name differs. Aliased imports keep the local alias and only change the imported symbol. |
| Approved static prop mappings | Maintainer-approved prop renames and literal value maps are applied. Dynamic or unapproved values are reported as prop review items. |
| Legacy LUI icon imports | Exact-name icons from @jda/lui-common-icon-library-mui5 move to the matching @by/icons/<namespace> export. |
| Not automated | Why |
|---|---|
| Material icon imports | @mui/icons-material is scanned and reported, but the write codemod does not replace it. |
| Registry recipes | Registry components may require installing or inspecting @by-es/<item> and adapting local composition. |
| Compose-from-primitives/manual-review | Components such as dialogs, cards, lists, object status, drawers, selects, and pagination need behavior/API review before replacement. |
| Styling and provider migrations | sx, styled, makeStyles, Tailwind setup, and ThemeProvider wiring are intentionally left to focused migration work. |
| Flag | Purpose |
|---|---|
--dir, -D | Root directory of source files to codemod (default: cwd). |
--gitignore, -G | Path to .gitignore (default: .gitignore). |
--write, -w | Write file edits. Omit for dry-run mode. |
--verbose, -V | Include unchanged files in the report. |
--format, -f, -O | table (default plain text) or json. |
--stdout | Print to the terminal only — do not write a report file. |
--out, -o / --out-dir / --name | Default docs/migration_codemod.txt or docs/migration_codemod.json by format (same pattern as scan). |
Recommended workflow:
- Run
migrate scan --format jsonfirst so the team can see all work, including manual review. - Run
migrate codemod --stdoutand review changed, skipped, and prop-review items. - Run
migrate codemod -won a focused slice. - Type-check, lint, test, and visually inspect the slice.
- Rerun
migrate scanto confirm the expected findings were removed.
Operating tips
| Tip | Detail |
|---|---|
| Train early | Getting started, Theming, by-es-llm-reference (Skills). |
| Preflight | Capture visual surprises when Tailwind first loads—screenshots or regression runs. |
| Imports | Prefer shallow @mui/material imports where possible — MUI minimizing bundle size. |
Resources
| Resource | Topic |
|---|---|
| Getting started | Adoption path and tools |
| Theming | ThemeProvider, tokens, density |
| Portal theme consumption | Shell themeMessage.themeObject (ThemeResponseMessage) |
| Installation | Peers, CSS, @source |
| ESLint Plugin | Migration, composition, accessibility, and opt-in style policies |
| Skills | setup-tailwind-theme-provider-skill, portal-theme-message-skill, LLM reference |
| Registry | components.json, REGISTRY_TOKEN |
| Components | Component index |
Related
MCP
Launch the published @by/experience-system-mcp package from Artifactory through an MCP client, and use its experience-system and governance tools in product projects.
Registry
Configure the Blue Yonder @by-es registry in components.json and add components with the shadcn CLI using npx, pnpx, or pnpm dlx.