Experience System
Overview

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.

DocUse it when…
Getting startedAdoption path, Tailwind, ThemeProvider, registry
InstallationExact install commands, @source, peers
ThemingTokens, 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.

TopicLegacy (typical)Target
ThemeMUI / LUI ThemeProvider, createTheme, sx, styled, makeStyles, LuiThemeProviderThemeProvider from @by/experience-system + tokens (theme.css)
Icons@jda/lui-common-icon-library-mui5@by/icons (paired with @by/experience-system)
PortalTheme hints from shell (historically MUI-shaped)Bridge into ThemeProvider — see Portal theme consumption

Expect three kinds of churn together:

Churn typeWhat helps
Importsby-es migrate scan (CLI)
StylingStyling below + Theming
CompositionGetting started, primitives vs registry blocks

Principles

PrincipleIn practice
Accessibility & behaviorFocus rings, keyboard paths, ARIA are part of the component contract—you compose Dialog, triggers, etc., rather than one opaque widget per variation (Radix-style).
CompositionPrefer small primitives (Field, Label, Button) + registry blocks your platform owns over indefinite MUI wrappers.
Reviewable stylingTailwind + semantic tokens make diffs easier than giant sx / styled() blobs (Theming).
Bundle surfaceNamed imports from @by/experience-system — shrink accidental MUI / CCL subgraphs as you migrate screens.

Strategy

TacticWhy
Vertical slicesStand up ThemeProvider, theme.css, @source per route/MFE—avoid a long-lived “migration branch” that blocks main.
Clear boundariesWrap new work with ThemeProvider at router/MFE edges; legacy keeps LuiThemeProvider / MUI ThemeProvider until touched.
Dependency hygieneConfirm React, Tailwind, MUI combinations your org still supports alongside @by/experience-system peers — Installation.
Portal / shellBridge themeMessage.themeObject into ThemeProviderPortal theme consumption, portal-theme-message-skill.
Parallel stacksExpect 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 liveTheme object: palette, theme.spacing, typography, sx / styled / makeStylesCSS: variables in theme.css, utilities, Button variant, ThemeProvider attributes
Dark / densityOften separate theme objects or flagsdark:, 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:

CommandStatus
migrate scanSupported — inventory legacy imports, summary aggregates, and per-import findings (see Scan below).
migrate reportSupportedsummary aggregates only (planning rollups, no per-import table) — from a scan or saved scan JSON (see Report below).
migrate codemodWork in progress — 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 scan

npx 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 scan

Optional version pin:

npx @by/experience-system@latest by-es migrate scan --dir ./src --format json

Dependency 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

--targetPackage universe
v4 (default)v3 — CCL v3 / MUI v5 list below
v3v2 — 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
FlagPurpose
--dir, -DDirectory to scan (default: cwd).
--gitignore, -GPath to .gitignore (default: .gitignore).
--verbose, -VVerbose logging.
--format, -f, -Otable (default), json, or markdown.
--stdoutPrint to the terminal only; do not write a file.
--out, -oExplicit output file path (relative to cwd); overrides docs/migration_scan.<ext>.
--out-dirFolder for the default migration_scan filename (default docs).
--nameDefault basename without extension (default migration_scan).
--target, -Tv3 or v4.
--top-files / --top-foldersCap hotspot listings in summary (default 20 each).
--help, -hHelp.
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 v3

Output

FormatTypical fileBest for
Tabledocs/migration_scan.txtPlain-text summary plus ASCII tables grouped by module in the findings.
jsondocs/migration_scan.jsonTickets, dashboards, scripting; summary + findings tree.
Markdowndocs/migration_scan.mdReadable 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).

PropertyDescription
summaryAggregates for planning (totals, byPackage / category / confidence counts, hotspots, links/registry/skills, warnings) — identical to migrate report JSON/Markdown (MigrateReportResult).
metaschemaVersion is 2.0; target (v3 | v4) matches --target; scanRoot is the absolute --dir.
findingsOrdered per-import rows (see findings[] below).

Meta object fields

FieldDescription
meta.schemaVersion2.0 for this contract; bump when the shape changes.
meta.targetv3 or v4 scan preset (matches --target).
meta.scanRootAbsolute path passed via --dir.

Findings entries

FieldDescription
findings[].filePathPath relative to scanRoot.
findings[].line / column1-based position (UTF-16 columns per TypeScript).
findings[].positionfilePath:line:column for quick lookup.
findings[].importKindnamed, default, or namespace.
findings[].isTypeOnlytrue for import type or type-only named bindings.
findings[].importedSymbolExported name (Button, default, \* for namespace).
findings[].localNameLocal binding (includes Foo as Bar aliases).
findings[].migrationActionHint 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[].migrationCategoryStructured matrix bucket (direct-replacement, registry-recipe, compose-from-primitives, theme-token, icon-migration, styling-migration, theme-provider-migration, manual-review, unknown).
findings[].confidencehigh | medium | low | unknown heuristic from guide text.
findings[].suggestedReplacementOptional mirror of actionable hints when present.
findings[].relatedDocsDesign-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).

FlagPurpose
--output, -Omarkdown (default) or json (summary JSON only).
--from-scan-jsonPath to migrate scan JSON; skip walking the tree. Match --target to the scan for correct package rollups.
--stdoutPrint to the terminal only — do not write a file.
--out, -o / --out-dir / --nameDefault docs/migration_report.md or docs/migration_report.json by format (same pattern as scan).
--top-files / --top-foldersCaps 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.md

Codemod (work in progress)

by-es migrate codemod will apply high-confidence mechanical edits (imports, icon swaps, etc.). Not implemented in current releases—running codemod prints an error and exits non-zero until it ships.


Operating tips

TipDetail
Train earlyGetting started, Theming, by-es-llm-reference (Skills).
PreflightCapture visual surprises when Tailwind first loads—screenshots or regression runs.
ImportsPrefer shallow @mui/material imports where possible — MUI minimizing bundle size.

Resources

ResourceTopic
Getting startedAdoption path and tools
ThemingThemeProvider, tokens, density
Portal theme consumptionShell themeMessage.themeObject (ThemeResponseMessage)
InstallationPeers, CSS, @source
Skillssetup-tailwind-theme-provider-skill, portal-theme-message-skill, LLM reference
Registrycomponents.json, REGISTRY_TOKEN
ComponentsComponent index