Mantle Priority → Intent Migration Guide
@ngrok/mantle renamed the tone prop from priority to intent across Button, Alert,
AlertDialog, and Toast, and made two further changes to the button family:
Button's value union changed:defaultis nowaccent(dangerandneutralare unchanged).ButtonandIconButtonnow require explicitappearanceandintentprops; there are no defaults.IconButtongained theintentaxis (accent | danger | neutral). ⚠️ A later change narrowedIconButtontointent="neutral"alone. This guide records the migration as it shipped, so read the icon button edge case below before you apply step 2 to one.
There are no deprecation aliases: priority is gone, and omitting appearance/intent on a
button is a type error. Every fix is mechanical and visual-parity-preserving, with exactly
one deliberate exception: a Button with appearance="filled" that omitted priority becomes
intent="neutral", the system's default primary action, instead of the accent tone it used to
inherit. Every other intentional restyle is separate follow-up work, never part of this
migration.
TL;DR: rename priority → intent everywhere it targets a mantle component; map Button's
"default" value to "accent"; stamp the old implicit defaults (appearance="outlined", Button
intent="accent", IconButton intent="neutral") onto every button that omitted them, except a
filled Button that omitted priority, which becomes intent="neutral".
Codemod
From a mantle checkout:
In a project that installs @ngrok/mantle:
The rules apply steps 2 to 4. They rename priority to intent on the listed components, map Button's "default" to "accent", and stamp the old implicit appearance and intent onto every Button and IconButton that omits them, with the filled-Button exception. A dynamic priority={value} renames too, but a value that can produce "default" keeps that string: map it by hand (step 5). A Button with a dynamic appearance and no tone also stays manual, because its old implicit intent depended on the value. Steps 6 and 7 stay manual, because a type import and a [data-priority] selector are not JSX attributes. The rules read a part through its import from @ngrok/mantle/button, under its own name or an alias. A part that reaches the file through a local re-export or a destructured namespace stays manual. The same holds for Alert, AlertDialog, Toast, and DataTable from their own subpaths. A function that binds one of those names to a parameter or a local shadows the import, so the rules leave that function alone. Pass --dry-run to print the rewrites first, and run your formatter after the rewrite.
You are an agent migrating a codebase. Do this:
- Find every JSX usage of these mantle components (including aliased/destructured imports and
wrappers that spread props onto them):
Button,IconButton,Alert.Root,AlertDialog.Root,AlertDialog.Action,AlertDialog.Cancel,Toast.Root, andDataTable.HeaderSortButton. - On each, rename the
priorityprop tointent. Values are unchanged except onButton(and components that forward Button props, likeAlertDialog.Action,AlertDialog.Cancel, andDataTable.HeaderSortButton): mappriority="default"→intent="accent". - On every
Buttonthat omitsappearance, addappearance="outlined". On everyButtonthat omittedpriority, addintent="accent", unless it hasappearance="filled", in which case addintent="neutral". The accent stamps are the old implicit defaults and preserve the exact previous rendering; the filled → neutral mapping is the migration's one deliberate restyle:appearance="filled" intent="neutral"is the system's default primary action going forward, so filled buttons that never chose a tone adopt it (they were accent-filled before). A filled button that explicitly wrotepriority="default"chose the accent tone; map it tointent="accent"per step 2. - On every
IconButtonthat omitsappearance, addappearance="outlined". On everyIconButton, addintent="neutral"unless a deliberate different intent is already known. ⚠️ IconButton's old rendering was neutral-toned:intent="neutral"is the parity value, NOT"accent". This is the one place the Button and IconButton rules differ. - Dynamic values:
priority={expression}becomesintent={expression}. If the expression can produce"default"(Button-family only), map it:intent={value === "default" ? "accent" : value}. Or better, fix the upstream type/value source. Types written asComponentProps<typeof Button>["priority"]become["intent"]and follow automatically. - Rename type imports:
import type { Priority } from "@ngrok/mantle/toast"→import type { ToastIntent } from "@ngrok/mantle/toast".ButtonIntentandButtonAppearanceare now exported from@ngrok/mantle/buttonif you need them. - Update
[data-priority]CSS selectors and test selectors to[data-intent](both Button and IconButton emitdata-intentnow). - Do not touch:
Badge'scolorprop;SplitButton.PrimaryAction(it never acceptedappearance/priority/intent); HTMLfetchpriority; the word "priority" in prose, comments, variable names, or non-mantle components. Local variables/types named after priority (e.g. aprioritystate feeding the prop) may keep their names (only the JSX prop and mantle type imports must change), but rename them too if it is cheap and clarifies. - After editing, run the project's formatter, linter, and type checker. The type checker is the safety net: every missed call site is a compile error, never a silent visual change.
The mapping table
Before → after
Bare Button (was implicit accent + outlined)
Filled Button without priority (the one deliberate restyle)
Button with priority
IconButton (parity value is neutral, not accent)
Alert, Toast, AlertDialog (rename only, values unchanged)
Wrapper components and dynamic values
Edge cases & gotchas
- IconButton ≠ Button parity values. A bare
Buttonbecomesintent="accent"; a bareIconButtonbecomesintent="neutral". Applying the Button rule to IconButton restyles every icon button from gray to blue: the most likely mistake in this migration. - IconButton takes
intent="neutral"alone. A later change narrowed the union, so step 2's rename turns anIconButton priority="accent"orpriority="danger"into a type error. Reach forButtonwhere the tone carries meaning, or drop tointent="neutral". - Filled Buttons that omitted
prioritychange pixels, on purpose. They map tointent="neutral", the system's default primary action, not the accent tone they used to inherit. This is the only mapping in this guide that changes rendering. Do not "fix" it back to accent wholesale; promote individual buttons tointent="accent"only where brand emphasis is the point. - Spread props. Objects spread onto buttons (
<Button {...buttonProps} />) may carrypriority; rename the key at the object's definition. If the object omitsappearance/intentand the JSX doesn't add them, add the explicit defaults at the JSX site. AlertDialog.Action/AlertDialog.Cancelkeep optionalappearance/intent; they are semantic wrappers whose defaults are the design (Action: filled + the dialog's tone, danger → danger and info → accent;Cancel: outlined + neutral). Only renamepriorityif a call site passed it; do not add explicit props to them.DataTable.HeaderSortButton/DataTable.RowExpandButtonkeep optionalappearance/intent; they are semantic wrappers whose defaults are the design (both: ghost + neutral). Only renamepriorityif a call site passed it; do not add explicit props to them: stampingappearance/intentonto sort headers restyles them.SplitButton.PrimaryActionandSplitButton.MenuTriggeraccept neitherappearancenorintent(no change).- Untyped/JS call sites (no TypeScript): the compiler cannot flag them. Grep for
priority=anddata-priorityafter the typed pass and fix survivors by the same rules. A leftoverpriorityprop is inert (forwarded to the DOM); a missingintenton Alert/Toast renders un-toned andToast.Icon/Alert.Iconwill throw at runtime. Treat any such crash as a missed rename. - Do not "clean up" while migrating.
intent="neutral"on 500 buttons may look redundant; it is now required. Choosing different intents happens in deliberate follow-up PRs.
How to verify
- Type check passes: with required props, every missed call site is a compile error.
grep -rn 'priority=' src/(or your app dir) returns no mantle-component hits: onlyfetchpriority, prose, or non-mantle code.grep -rn 'data-priority'returns nothing.- Spot-check visually: buttons render identically to before, except filled buttons that had
no
priority, which are now neutral-filled (the deliberate restyle above); icon buttons are still gray. - Run the project's formatter, linter, and tests.