Label to Badge codemod

Migrates the deprecated Label component to Badge, and Badge's deprecated isOutline/isEmphasized props to their color replacements — the removals land in 3.0.0, and the codemod is run as part of that upgrade. (It also works from 2.190.0 onward, where Badge absorbed Label's colors, bold text, and status indicator, if you want to migrate ahead of the major.)

What it rewrites

Label → Badge (React Label, Ember PlumaLabel):

  • The component and its pluma import are renamed to Badge/PlumaBadge. If Badge is already imported (React), the Label specifier is dropped and usages reuse the existing local name.
  • A Label without a color gains color="grey" — Label defaulted to grey, and the explicit color keeps that intent readable at the call site. On 3.0 (where this codemod runs as part of the upgrade) a bare Badge also renders grey — neutral is its semantic alias — so the rendered output is preserved either way; the explicit value additionally guards against the default ever changing.
  • color="outline" becomes color="neutral-outline".
  • Every other literal color carries over unchanged and renders pixel-identical.

Badge deprecated props:

  • isEmphasized becomes color="accent".
  • isOutline becomes color="neutral-outline".
  • Both together become color="accent-outline".
  • Literal false values are simply removed.

What it flags instead of rewriting

Dynamic expressions are left unchanged and reported as warnings for manual review:

  • A dynamic Label color (e.g. @color={{if @tracked 'outline' 'grey'}}) — "outline" must become "neutral-outline" by hand. Expressions that can yield undefined render the same grey either way on 3.0 (neutral aliases grey) — review them only for the outline case. Trace the binding to its source when fixing these: the 'outline' value often lives in a model, constant, or type union in another file, so grep the app for 'outline' after running.
  • Dynamic isOutline/isEmphasized bindings.
  • Elements that already combine color with the deprecated props (invalid — throws at runtime).

Running it

Run it through the Pluma CLI from inside your consuming app. Point the React codemod at source directories (./src, ./app) rather than a workspace root — jscodeshift walks everything it is handed, including node_modules:

pnx @customerio/pluma-cli@latest upgrade --codemod label-to-badge react ./src
pnx @customerio/pluma-cli@latest upgrade --codemod label-to-badge ember ./app ./addon

Both codemods support dry runs. The React codemod uses jscodeshift flags, so --dry/-d skips writes and -p prints the transformed output:

pnx @customerio/pluma-cli@latest upgrade --codemod label-to-badge react --dry -p ./src

The Ember codemod has its own --dry flag:

pnx @customerio/pluma-cli@latest upgrade --codemod label-to-badge ember --dry ./app

Limitations

  • Only angle-bracket / JSX invocations are handled.
  • LabelProps type imports are not rewritten — replace with BadgeProps manually.
  • React Label aliases (import { Label as X }) keep their local name; the import specifier switches to Badge as X.
  • Ember imports are rewritten for the standard forms — named specifiers from @customerio/pluma-components/ember and the /ember/label subpath default import. Renamed templates whose PlumaLabel import takes any other form are flagged for a manual import update.
  • Label rendered a <span>; on the previous major Badge rendered a <div>, so migrated badges inside text flow (paragraphs, headings, links, buttons) are invalid phrasing content until the 3.0 upgrade, where Badge renders a <span> by default (see the breaking changes); pass as="span" in the interim where it matters.