Skip to main content
react-md

Migrate from v5 to v6

There were a lot of breaking changes between v5 and v6 of react-md, but a good amount can be automated using the codemod. The migrations will be listed on this page and prefixed with an icon:

Make sure to have committed or stashed any working changes before running the codemod to make reviewing changes easier and having a fallback if everything goes wrong. If you use a formatter, make sure to run it against all files afterwards to reduce the diffs as well.

Before getting started, remove all the @react-md/* packages and upgrade to the latest react-md version.

npm uninstall \
  react-md \
  @react-md/alert \
  @react-md/app-bar \
  @react-md/autocomplete \
  @react-md/avatar \
  @react-md/badge \
  @react-md/button \
  @react-md/card \
  @react-md/chip \
  @react-md/dialog \
  @react-md/divider \
  @react-md/elevation \
  @react-md/expansion-panel \
  @react-md/form \
  @react-md/icon \
  @react-md/layout \
  @react-md/link \
  @react-md/list \
  @react-md/material-icons \
  @react-md/media \
  @react-md/menu \
  @react-md/overlay \
  @react-md/portal \
  @react-md/progress \
  @react-md/sheet \
  @react-md/states \
  @react-md/table \
  @react-md/tabs \
  @react-md/theme \
  @react-md/tooltip \
  @react-md/transition \
  @react-md/tree \
  @react-md/typography \
  @react-md/utils
pnpm remove \
  react-md \
  @react-md/alert \
  @react-md/app-bar \
  @react-md/autocomplete \
  @react-md/avatar \
  @react-md/badge \
  @react-md/button \
  @react-md/card \
  @react-md/chip \
  @react-md/dialog \
  @react-md/divider \
  @react-md/elevation \
  @react-md/expansion-panel \
  @react-md/form \
  @react-md/icon \
  @react-md/layout \
  @react-md/link \
  @react-md/list \
  @react-md/material-icons \
  @react-md/media \
  @react-md/menu \
  @react-md/overlay \
  @react-md/portal \
  @react-md/progress \
  @react-md/sheet \
  @react-md/states \
  @react-md/table \
  @react-md/tabs \
  @react-md/theme \
  @react-md/tooltip \
  @react-md/transition \
  @react-md/tree \
  @react-md/typography \
  @react-md/utils
yarn remove \
  react-md \
  @react-md/alert \
  @react-md/app-bar \
  @react-md/autocomplete \
  @react-md/avatar \
  @react-md/badge \
  @react-md/button \
  @react-md/card \
  @react-md/chip \
  @react-md/dialog \
  @react-md/divider \
  @react-md/elevation \
  @react-md/expansion-panel \
  @react-md/form \
  @react-md/icon \
  @react-md/layout \
  @react-md/link \
  @react-md/list \
  @react-md/material-icons \
  @react-md/media \
  @react-md/menu \
  @react-md/overlay \
  @react-md/portal \
  @react-md/progress \
  @react-md/sheet \
  @react-md/states \
  @react-md/table \
  @react-md/tabs \
  @react-md/theme \
  @react-md/tooltip \
  @react-md/transition \
  @react-md/tree \
  @react-md/typography \
  @react-md/utils
npm install react-md @react-md/material-icons
pnpm add react-md @react-md/material-icons
yarn add react-md @react-md/material-icons

Codemods

If you just want to try auto-migrating everything through the codemods and handling the TODOs/manual steps afterwards, you can run the following commands:

npx @react-md/codemod migrate/v5-to-v6

You will be prompted to provide the Javascript parser, which files/folder to run the codemod on, and if everything should be auto-confirmed.

Javascript/Typescript Migration

✅ Update all imports to react-md

This step is required for any other codemods to work. The codemods only look for the react-md package name.

npx @react-md/codemod v5-to-v6/to-react-md-imports

To make the migration easier, the codemod will begin by converting all scoped package imports (@react-md/*) into the react-md package import.

+import {
+  AppBar,
+  AppBarNav,
+  ArrowBackSVGIcon,
+  Button,
+  Dialog,
+  DialogContent,
+  MediaContainer,
+} from "react-md";
+
 import type { ReactElement } from "react";
 import { useState } from "react";
-import { AppBar, AppBarNav } from "@react-md/app-bar";
-import { Button } from "@react-md/button";
-import { Dialog, DialogContent } from "@react-md/dialog";
-import { ArrowBackSVGIcon } from "@react-md/material-icons";
-import { MediaContainer } from "@react-md/media";

 import AppBarTitle from "./AppBarTitle";
 import styles from "./FullPageExample.module.scss";

🔧 Hardcode any scssVariables usage

npx @react-md/codemod v5-to-v6/hardcode-scss-variables

This codemod converts any @react-md/*/dist/scssVariables into hard coded constants since it no longer exists.

-import alertVariables from "@react-md/alert/dist/scssVariables";
-import appBarVariables from "@react-md/app-bar/dist/scssVariables";
-import mediaVariables from "@react-md/media/dist/scssVariables";
 import shouldNotChange from "another/scssVariables";

-const color = alertVariables["rmd-alert-theme-values"].color;
-const backgroundColor =
-  alertVariables["rmd-alert-theme-values"]["background-color"];
+const color = "#fff";
+const backgroundColor = "#323232";

-const margin = alertVariables["rmd-snackbar-margin"];
-const zIndex = alertVariables["rmd-snackbar-z-index"];
+const margin = "1rem";
+const zIndex = 40;

-const titleKeyline = appBarVariables["rmd-app-bar-title-keyline"];
+const titleKeyline = "4.5rem";

-const mediaOverlayPositions = mediaVariables["rmd-media-overlay-positions"];
-const mediaOverlayPosition = mediaVariables["rmd-media-overlay-positions"][2];
-const aspectRatio = mediaVariables["rmd-media-default-aspect-ratios"]['"16-9"'];
+const mediaOverlayPositions = [
+  "top",
+  "right",
+  "bottom",
+  "left",
+  "middle",
+  "center",
+  "absolute-center",
+];
+const mediaOverlayPosition = "bottom";
+const aspectRatio = "16 9";

 function Example() {
   return (
     <div
       style={{
-        backgroundColor:
-          appBarVariables["rmd-app-bar-primary-background-color"],
+        backgroundColor: "var(--rmd-theme-primary, #9c27b0)",
       }}
     />
   );

✅ Remove deprecated FontIcon props

npx @react-md/codemod v5-to-v6/icon/remove-deprecated-font-icon-props

The forceSize and forceFontSize props were removed from the FontIcon component.

     <>
       <FontIcon>material_unchanged</FontIcon>
       <FontIcon iconClassName="fa fa-star" />
-      <FontIcon iconClassName="fa fa-star" className={styles.icon} forceSize />
-      <FontIcon iconClassName={styles.fontIcon} forceFontSize />
-      <FontIcon
-        iconClassName={styles.fontIcon}
-        className={styles.icon}
-        forceSize
-        forceFontSize
-      />
+      <FontIcon iconClassName="fa fa-star" className={styles.icon} />
+      <FontIcon iconClassName={styles.fontIcon} />
+      <FontIcon iconClassName={styles.fontIcon} className={styles.icon} />
     </>
   );
 }

✅ Update Material Icons

The @react-md/material-icons package was updated to support all the new icons and material symbols but only exports SVG icon component versions. Choose one of the following migrations:

✅ to-svg
npx @react-md/codemod v5-to-v6/material-icons/to-svg

Converts all material icons into the SVG components in v6.0.0.

+import ArrowDropDownIcon from "@react-md/material-icons/ArrowDropDownIcon";
+import ArrowUpwardIcon from "@react-md/material-icons/ArrowUpwardIcon";
+import CheckBoxIcon from "@react-md/material-icons/CheckBoxIcon";
+import CheckIcon from "@react-md/material-icons/CheckIcon";
+import ErrorOutlineIcon from "@react-md/material-icons/ErrorOutlineIcon";
+import FileUploadIcon from "@react-md/material-icons/FileUploadIcon";
+import KeyboardArrowDownIcon from "@react-md/material-icons/KeyboardArrowDownIcon";
+import KeyboardArrowLeftIcon from "@react-md/material-icons/KeyboardArrowLeftIcon";
+import KeyboardArrowRightIcon from "@react-md/material-icons/KeyboardArrowRightIcon";
+import MenuIcon from "@react-md/material-icons/MenuIcon";
+import NotificationsIcon from "@react-md/material-icons/NotificationsIcon";
+import RadioButtonCheckedIcon from "@react-md/material-icons/RadioButtonCheckedIcon";
+import RemoveRedEyeIcon from "@react-md/material-icons/RemoveRedEyeIcon";
 import type { ReactElement, ReactNode } from "react";
 import { Link, useLocation } from "react-router-dom";
 import {
-  ArrowDropDownSVGIcon,
-  ArrowUpwardSVGIcon,
-  CheckBoxSVGIcon,
-  CheckSVGIcon,
   ConfiguredIcons,
   Configuration,
-  ErrorOutlineSVGIcon,
-  FileUploadSVGIcon,
-  KeyboardArrowDownSVGIcon,
-  KeyboardArrowLeftSVGIcon,
-  KeyboardArrowRightFontIcon,
   Layout as RMDLayout,
-  MenuSVGIcon,
-  NotificationsSVGIcon,
-  RadioButtonCheckedSVGIcon,
-  RemoveRedEyeSVGIcon as MyTestIcon,
   useLayoutNavigation,
 } from "react-md";

 import navItems from "./navItems";

 const icons: ConfiguredIcons = {
-  back: <KeyboardArrowLeftSVGIcon />,
-  checkbox: <CheckBoxSVGIcon />,
-  dropdown: <ArrowDropDownSVGIcon />,
-  error: <ErrorOutlineSVGIcon />,
-  expander: <KeyboardArrowDownSVGIcon />,
-  forward: <KeyboardArrowRightFontIcon />,
-  menu: <MenuSVGIcon />,
-  notification: <NotificationsSVGIcon />,
-  password: <MyTestIcon />,
-  radio: <RadioButtonCheckedSVGIcon />,
-  selected: <CheckSVGIcon />,
-  sort: <ArrowUpwardSVGIcon />,
-  upload: <FileUploadSVGIcon></FileUploadSVGIcon>,
+  back: <KeyboardArrowLeftIcon />,
+  checkbox: <CheckBoxIcon />,
+  dropdown: <ArrowDropDownIcon />,
+  error: <ErrorOutlineIcon />,
+  expander: <KeyboardArrowDownIcon />,
+  forward: <KeyboardArrowRightIcon />,
+  menu: <MenuIcon />,
+  notification: <NotificationsIcon />,
+  password: <RemoveRedEyeIcon />,
+  radio: <RadioButtonCheckedIcon />,
+  selected: <CheckIcon />,
+  sort: <ArrowUpwardIcon />,
+  upload: <FileUploadIcon></FileUploadIcon>,
 };
✅ to-font
npx @react-md/codemod v5-to-v6/material-icons/to-font

Converts all material icons into the new MaterialIcon component.

@@ -1,39 +1,27 @@
 import type { ReactElement, ReactNode } from "react";
 import { Link, useLocation } from "react-router-dom";
 import {
-  ArrowDropDownFontIcon,
-  ArrowUpwardFontIcon,
-  CheckBoxFontIcon,
-  CheckFontIcon,
-  ConfiguredIcons,
   Configuration,
-  ErrorOutlineFontIcon,
-  FileUploadFontIcon,
-  KeyboardArrowDownFontIcon,
-  KeyboardArrowLeftFontIcon,
-  KeyboardArrowRightFontIcon,
+  ConfiguredIcons,
   Layout as RMDLayout,
-  MenuFontIcon,
-  NotificationsFontIcon,
-  RadioButtonCheckedFontIcon,
-  RemoveRedEyeFontIcon as MyTestIcon,
+  MaterialIcon,
   useLayoutNavigation,
 } from "react-md";

 import navItems from "./navItems";

 const icons: ConfiguredIcons = {
-  back: <KeyboardArrowLeftFontIcon />,
-  checkbox: <CheckBoxFontIcon />,
-  dropdown: <ArrowDropDownFontIcon />,
-  error: <ErrorOutlineFontIcon />,
-  expander: <KeyboardArrowDownFontIcon />,
-  forward: <KeyboardArrowRightFontIcon />,
-  menu: <MenuFontIcon />,
-  notification: <NotificationsFontIcon />,
-  password: <MyTestIcon />,
-  radio: <RadioButtonCheckedFontIcon />,
-  selected: <CheckFontIcon />,
-  sort: <ArrowUpwardFontIcon />,
-  upload: <FileUploadFontIcon></FileUploadFontIcon>,
+  back: <MaterialIcon name="keyboard_arrow_left" />,
+  checkbox: <MaterialIcon name="check_box" />,
+  dropdown: <MaterialIcon name="arrow_drop_down" />,
+  error: <MaterialIcon name="error_outline" />,
+  expander: <MaterialIcon name="keyboard_arrow_down" />,
+  forward: <MaterialIcon name="keyboard_arrow_right" />,
+  menu: <MaterialIcon name="menu" />,
+  notification: <MaterialIcon name="notifications" />,
+  password: <MaterialIcon name="remove_red_eye" />,
+  radio: <MaterialIcon name="radio_button_checked" />,
+  selected: <MaterialIcon name="check" />,
+  sort: <MaterialIcon name="arrow_upward" />,
+  upload: <MaterialIcon name="file_upload" />,
 };
✅ to-symbol
npx @react-md/codemod v5-to-v6/material-icons/to-symbol

Converts all material icons into the new MaterialSymbol component.

 import type { ReactElement, ReactNode } from "react";
 import { Link, useLocation } from "react-router-dom";
 import {
-  ArrowDropDownFontIcon,
-  ArrowUpwardFontIcon,
-  CheckBoxFontIcon,
-  CheckFontIcon,
-  ConfiguredIcons,
   Configuration,
-  ErrorOutlineFontIcon,
-  FileUploadFontIcon,
-  KeyboardArrowDownFontIcon,
-  KeyboardArrowLeftFontIcon,
-  KeyboardArrowRightFontIcon,
+  ConfiguredIcons,
   Layout as RMDLayout,
-  MenuFontIcon,
-  NotificationsFontIcon,
-  RadioButtonCheckedFontIcon,
-  RemoveRedEyeFontIcon as MyTestIcon,
+  MaterialSymbol,
   useLayoutNavigation,
 } from "react-md";

 import navItems from "./navItems";

 const icons: ConfiguredIcons = {
-  back: <KeyboardArrowLeftFontIcon />,
-  checkbox: <CheckBoxFontIcon />,
-  dropdown: <ArrowDropDownFontIcon />,
-  error: <ErrorOutlineFontIcon />,
-  expander: <KeyboardArrowDownFontIcon />,
-  forward: <KeyboardArrowRightFontIcon />,
-  menu: <MenuFontIcon />,
-  notification: <NotificationsFontIcon />,
-  password: <MyTestIcon />,
-  radio: <RadioButtonCheckedFontIcon />,
-  selected: <CheckFontIcon />,
-  sort: <ArrowUpwardFontIcon />,
-  upload: <FileUploadFontIcon></FileUploadFontIcon>,
+  back: <MaterialSymbol name="keyboard_arrow_left" />,
+  checkbox: <MaterialSymbol name="check_box" />,
+  dropdown: <MaterialSymbol name="arrow_drop_down" />,
+  error: <MaterialSymbol name="error_outline" />,
+  expander: <MaterialSymbol name="keyboard_arrow_down" />,
+  forward: <MaterialSymbol name="keyboard_arrow_right" />,
+  menu: <MaterialSymbol name="menu" />,
+  notification: <MaterialSymbol name="notifications" />,
+  password: <MaterialSymbol name="remove_red_eye" />,
+  radio: <MaterialSymbol name="radio_button_checked" />,
+  selected: <MaterialSymbol name="check" />,
+  sort: <MaterialSymbol name="arrow_upward" />,
+  upload: <MaterialSymbol name="file_upload" />,
 };

Update the scoped packages

Alert ❌

There are a lot of breaking changes for the alert components and API. There are some auto-migrations available, but it is recommended to change a few things manually to adapt to the new API. The best way to understand the changes is to check out the new Snackbar Examples, but here's a summary of the changes:

Here's an example diff for converting the "Simple Message Queue" demo in v5 to the v6 implementation:

+import { Snackbar } from "@react-md/core/snackbar/Snackbar";
+import { addToast, type CreateToastOptions } from "@react-md/core/snackbar/ToastManager";
 import type { ReactElement } from "react";
-import type { ToastMessage } from "@react-md/alert";
-import { MessageQueue, useAddMessage } from "@react-md/alert";
 import { Button } from "@react-md/button";
 import { Form, Radio, useChoice } from "@react-md/form";

 const SINGLE_LINE = "SINGLE_LINE";
 const SINGLE_LINE_ACTION = "SINGLE_LINE_ACTION";
 const TWO_LINES = "TWO_LINES";
 const TWO_LINES_ACTION = "TWO_LINES_ACTION";
 const TWO_LINES_STACKED = "TWO_LINES_STACKED";

 type MessageKeys =
   | typeof SINGLE_LINE
   | typeof SINGLE_LINE_ACTION
   | typeof TWO_LINES
   | typeof TWO_LINES_ACTION
   | typeof TWO_LINES_STACKED;
-type MessageRecord = Record<MessageKeys, ToastMessage>;
+type MessageRecord = Record<MessageKeys, CreateToastOptions>;

 const messages: MessageRecord = {
   [SINGLE_LINE]: {
     children: "This is an example message",
   },
   [SINGLE_LINE_ACTION]: {
     action: "Action",
     children: "This is an example message",
   },
   [TWO_LINES]: {
-    twoLines: true,
+    // this is optional
+    multiline: true,
     children: (
       <>
         <p>This is an example message</p>
         <p>With a second line of text.</p>
       </>
     ),
   },
   [TWO_LINES_ACTION]: {
     action: "Action",
-    twoLines: true,
+    // this is optional
+    multiline: true,
     children: (
       <>
         <p>This is an example message</p>
         <p>With a second line of text.</p>
       </>
     ),
   },
   [TWO_LINES_STACKED]: {
     action: "Action",
     stacked: true,
-    twoLines: true,
+    // this is optional
+    multiline: true,
     children: (
       <>
         <p>This is an example message</p>
         <p>With a second line of text.</p>
       </>
     ),
   },
 };

 function SimpleMessageQueue(): ReactElement {
-  const addMessage = useAddMessage();
   const [key, handleKeyChange] = useChoice<MessageKeys>(SINGLE_LINE);

   return (
-    <Form onSubmit={() => addMessage(messages[key])}>
+    <Form onSubmit={() => addToast(messages[key])}>
       <Radio
         id="mqr-1"
         name="message"
         value={SINGLE_LINE}
         checked={key === SINGLE_LINE}
         onChange={handleKeyChange}
         label="Single Line Message"
       />
       <Radio
         id="mqr-2"
         name="message"
         value={SINGLE_LINE_ACTION}
         checked={key === SINGLE_LINE_ACTION}
         onChange={handleKeyChange}
         label="Single Line Message with Action"
       />
       <Radio
         id="mqr-3"
         name="message"
         value={TWO_LINES}
         checked={key === TWO_LINES}
         onChange={handleKeyChange}
         label="Two Line Message"
       />
       <Radio
         id="mqr-4"
         name="message"
         value={TWO_LINES_ACTION}
         checked={key === TWO_LINES_ACTION}
         onChange={handleKeyChange}
         label="Two Line Message with Action"
       />
       <Radio
         id="mqr-5"
         name="message"
         value={TWO_LINES_STACKED}
         checked={key === TWO_LINES_STACKED}
         onChange={handleKeyChange}
         label="Two Line Message with Stacked Action"
       />
       <Button id="mqr-submit" type="submit" theme="primary">
         Add Message
       </Button>
     </Form>
   );
 }

 export default function SimpleMessageQueueContainer(): ReactElement {
   return (
+    <>
-    <MessageQueue id="simple-message-queue">
       <SimpleMessageQueue />
+      <Snackbar />
+    <>
-    </MessageQueue>
   );
 }

App Bar

🔧 remove-class-name-constants
npx @react-md/codemod v5-to-v6/app-bar/remove-class-name-constants

The APP_BAR_OFFSET_* constants were removed and custom styles are required to add any offset going forward. The codemod will apply the following diff:

+// TODO: Add styles for app bar offset
 import cn from "classnames";
 import { useState } from "react";
-import {
-  APP_BAR_OFFSET_CLASSNAME,
-  APP_BAR_OFFSET_DENSE_CLASSNAME,
-  APP_BAR_OFFSET_PROMINENT_CLASSNAME,
-  APP_BAR_OFFSET_PROMINENT_DENSE_CLASSNAME,
-} from "react-md";
 import styles from "./styles.module.scss";

 export default function Example() {
   const [dense, setDense] = useState(false);
   const [prominent, setProminent] = useState(false);

-  return (
-    <div
-      className={cn(
-        styles.content,
-        {
-          [APP_BAR_OFFSET_CLASSNAME]: !dense && !prominent,
-          [APP_BAR_OFFSET_DENSE_CLASSNAME]: dense && !prominent,
-          [APP_BAR_OFFSET_PROMINENT_CLASSNAME]: !dense && prominent,
-          [APP_BAR_OFFSET_PROMINENT_DENSE_CLASSNAME]: dense && prominent,
-        },
-        !dense && !prominent && APP_BAR_OFFSET_CLASSNAME,
-        dense && !prominent && APP_BAR_OFFSET_DENSE_CLASSNAME,
-        !dense && prominent && APP_BAR_OFFSET_PROMINENT_CLASSNAME,
-        dense && prominent && APP_BAR_OFFSET_PROMINENT_DENSE_CLASSNAME
-      )}
-    >
-      content
-    </div>
-  );
+  return <div className={cn(styles.content, {})}>content</div>;
 }

If the content should still be offset by the app bar's height, apply the following styles:

@use "react-md";

.container {
  // use the current var(--rmd-app-bar-height) as the padding top
  @include react-md.app-bar-use-var(padding-top, height);

  // or choose one of the static values:
  padding-top: react-md.$app-bar-height;
  padding-top: react-md.$dense-height;
  padding-top: react-md.$prominent-height;
  padding-top: react-md.$prominent-dense-height;
}
✅ remove-use-action-class-name
npx @react-md/codemod v5-to-v6/app-bar/remove-use-action-class-name

The useActionClassName hook was removed since it is no longer needed for spacing within an AppBar.

-import { useActionClassName } from "react-md";
 import SomeComponent from "./SomeComponent";
 import styles from "./styles.module.scss";

 export function Example() {
-  const className1 = useActionClassName();
-  const className2 = useActionClassName({ first: true });
-  const className3 = useActionClassName({ last: true });
-  const className4 = useActionClassName({ className: styles.example });
+  const className4 = styles.example;

   return (
     <>
-      <div className={className1} />
-      <div className={className2} />
-      <div className={className3} />
+      <div />
+      <div />
+      <div />
       <div className={className4} />
       <SomeComponent
-        className1={useActionClassName()}
-        className2={useActionClassName({ first: true })}
-        className3={useActionClassName({ last: true })}
-        className4={useActionClassName({ className: styles.example })}
+        className4={styles.example}
         objectExample={{
-          className1: useActionClassName(),
-          className2: useActionClassName({ first: true }),
-          className3: useActionClassName({ last: true }),
-          className4: useActionClassName({ className: styles.example }),
+          className4: styles.example,
         }}
       />
     </>
✅ replace-nav-and-action-with-button
npx @react-md/codemod v5-to-v6/app-bar/replace-nav-and-action-with-button

The AppBarAction and AppBarNav components were removed and can be replaced with a Button.

 import type { ReactElement } from "react";
 import {
   AppBar,
-  AppBarAction,
-  AppBarNav,
   AppBarTitle,
+  Button,
   MenuSVGIcon,
   MoreVertSVGIcon,
   SearchSVGIcon,
@@ -19,33 +18,21 @@ export default function Demo(): ReactElement {
     <Container>
       {themes.map((theme, i) => (
         <AppBar id={`simple-usage-app-bar-${i}`} theme={theme} key={theme}>
-          <AppBarNav
-            id={`simple-usage-nav-${i}`}
-            aria-label="Navigation"
-            inheritColor
-          >
+          <Button id={`simple-usage-nav-${i}`} aria-label="Navigation">
             <MenuSVGIcon />
-          </AppBarNav>
+          </Button>
           <AppBarTitle
             id={`simple-usage-title-${i}`}
             className="rmd-typography--capitalize"
           >
             {theme}
           </AppBarTitle>
-          <AppBarAction
-            id={`simple-usage-search-${i}`}
-            first
-            aria-label="Search"
-          >
+          <Button id={`simple-usage-search-${i}`} aria-label="Search">
             <SearchSVGIcon />
-          </AppBarAction>
-          <AppBarAction
-            id={`simple-usage-kebab-${i}`}
-            last
-            aria-label="Actions"
-          >
+          </Button>
+          <Button id={`simple-usage-kebab-${i}`} aria-label="Actions">
             <MoreVertSVGIcon />
-          </AppBarAction>
+          </Button>
         </AppBar>
       ))}
     </Container>
✅ update-app-bar-props
npx @react-md/codemod v5-to-v6/app-bar/update-app-bar-props

The following props have changed for the AppBar component:

 function Example() {
   return (
     <>
-      <AppBar component="div">Hello</AppBar>
-      <AppBar fixed={true}>Hello</AppBar>
-      <AppBar fixed fixedElevation={false}>
-        Hello
-      </AppBar>
-      <AppBar fixed={false} fixedElevation>
-        Hello
-      </AppBar>
-      <AppBar flexWrap inheritColor>
+      <AppBar as="div">Hello</AppBar>
+      <AppBar position="fixed">Hello</AppBar>
+      <AppBar position="fixed" disableFixedElevation>
         Hello
       </AppBar>
+      <AppBar>Hello</AppBar>
+      <AppBar>Hello</AppBar>
     </>
   );
 }
✅ update-app-bar-title-props
npx @react-md/codemod v5-to-v6/app-bar/update-app-bar-title-props

The following props have changed for the AppBarTitle component:

 function Example() {
   return (
     <>
-      <AppBarTitle noWrap>Hello</AppBarTitle>
-      <AppBarTitle noWrap={true}>Hello</AppBarTitle>
-      <AppBarTitle noWrap={false}>Hello</AppBarTitle>
-      <AppBarTitle keyline>Hello</AppBarTitle>
-      <AppBarTitle keyline={true}>Hello</AppBarTitle>
-      <AppBarTitle keyline={false}>Hello</AppBarTitle>
+      <AppBarTitle textOverflow="nowrap">Hello</AppBarTitle>
+      <AppBarTitle textOverflow="nowrap">Hello</AppBarTitle>
+      <AppBarTitle>Hello</AppBarTitle>
+      <AppBarTitle keyline="nav">Hello</AppBarTitle>
+      <AppBarTitle keyline="nav">Hello</AppBarTitle>
+      <AppBarTitle>Hello</AppBarTitle>
     </>
   );
 }

Auto Complete ❌

The AutoComplete has a completely new API and has been renamed to Autocomplete. It is recommended to check out the Autocomplete Demos to see how to use the new API.

The main breaking changes are:

🔧 update-simple-props
npx @react-md/codemod v5-to-v6/autocomplete/update-simple-props

This codemod will attempt to fix all the breaking changes and add TODOs for all the manual steps.

  +// TODO: Ensure the `Autocomplete` options are strings or add the `getOptionLabel` prop
 import { type ReactElement, useState } from "react";
-import { AutoComplete } from "react-md";
+import { Autocomplete } from "react-md";

 const fruits = [
   "Apple",
   "Apricot",
   "Banana",
   "Blueberry",
   "Cranberry",
   "Kiwi",
   "Peach",
   "Plum",
   "Strawberry",
 ];

 export default function CustomFilterFunction(): ReactElement {
   const [value, setValue] = useState("");
   return (
     <>
-      <AutoComplete
+      <Autocomplete
         id="simple-autocomplete-1"
         label="Case insensitive"
         placeholder="Apple"
-        value={value}
+        query={value}
         onChange={(event) => {
           setValue(event.currentTarget.value)
         }}
-        data={fruits}
+        options={fruits}
-        filter={(query, data, options) => {
+        filter={({ query, list: data, ...options }) => {
           return data;
         }}
-        clearOnAutoComplete
+        updateQueryOnSelect="clear"
-        onAutoComplete={({ value, index, result, dataIndex, filteredData }) => {
+        onValueChange={(value) => {
           // do something
         }}
-        listboxStyle={{ color: "red" }}
-        listboxClassName="custom-class-name"
-        listboxWidth="auto"
-        anchor={BELOW_CENTER_ANCHOR}
-        vwMargin={20}
-        vhMargin={20}
-        xMargin={12}
-        yMargin={12}
-        closeOnResize={false}
-        transformOrigin={false}
-        closeOnScroll={false}
-        preventOverlap={false}
-        disableSwapping={false}
-        disableVHBounds={false}
+        listboxProps={{
+          style: { color: "red" },
+          className: "custom-class-name",
+          width: "auto",
+          anchor: BELOW_CENTER_ANCHOR,
+          vwMargin: 20,
+          vhMargin: 20,
+          xMargin: 12,
+          yMargin: 12,
+          closeOnResize: false,
+          transformOrigin: false,
+          closeOnScroll: false,
+          preventOverlap: false,
+          disableSwapping: false,
+          disableVHBounds: false,
+        }}
       />
     </>
   );
 }

Avatar 🎉

No breaking changes!

Badge

🎨 remove-badge-container
npx @react-md/codemod v5-to-v6/badge/remove-badge-container

The BadgeContainer was removed and custom styles must be used instead.

 import type { ReactElement } from "react";
-import {
-  Badge,
-  BadgeContainer,
-  Button,
-  NotificationsSVGIcon,
-  Typography,
-} from "react-md";
+import { Badge, Button, NotificationsSVGIcon, Typography } from "react-md";

 import { COPYRIGHT } from "constants/unicode";

@@ -14,15 +8,27 @@ import styles from "./CustomizingBadges.module.scss";
 export default function CustomizingBadges(): ReactElement {
   return (
     <>
-      <BadgeContainer className={styles.container}>
+      <span
+        className={styles.container}
+        style={{
+          display: "inline-flex",
+          position: "relative",
+        }}
+      >
         {/* since the badge is presentational, don't add the `aria-describedby` value */}
         <Typography>Some amazing product</Typography>
         <Badge id="copyright-badge" theme="clear">
           {COPYRIGHT}
         </Badge>
-      </BadgeContainer>
+      </span>
       {/* this is _basically_ the `BadgedButton` component except with an extra `<span>` */}
-      <BadgeContainer className={styles.custom}>
+      <span
+        className={styles.custom}
+        style={{
+          display: "inline-flex",
+          position: "relative",
+        }}
+      >
         <Button
           id="custom-badged-button"
           aria-describedby="custom-badged-button-badge"
@@ -31,7 +37,7 @@ export default function CustomizingBadges(): ReactElement {
           <NotificationsSVGIcon />
         </Button>
         <Badge id="custom-badged-button-badge">8</Badge>
-      </BadgeContainer>
+      </span>
     </>
   );
 }
🔧 update-badge
npx @react-md/codemod v5-to-v6/badge/update-badge

The BadgedButton was removed and instead just use the Button and Badge components together.

 import { type ReactElement } from "react";
-import { BadgedButton } from "react-md";
+import { Badge, Button, getIcon } from "react-md";
 import { NotificationsSVGIcon } from "@react-md/material-icons";

 import styles from "./SimpleExamples.module.scss";
@@ -7,16 +7,24 @@ import styles from "./SimpleExamples.module.scss";
 export default function Example(): ReactElement {
   return (
     <>
-      <BadgedButton id="badged-button-1" className={styles.container}>
-        3
-      </BadgedButton>
-      <BadgedButton
+      <Button
+        id="badged-button-1"
+        className={styles.container}
+        aria-label="Notifications"
+        buttonType="icon"
+      >
+        {getIcon("notification")}
+        <Badge>3</Badge>
+      </Button>
+      <Button
         id="badged-button-2"
         className={styles.container}
-        buttonChildren={<NotificationsSVGIcon />}
+        aria-label="Notifications"
+        buttonType="icon"
       >
-        7
-      </BadgedButton>
+        <NotificationsSVGIcon />
+        <Badge>7</Badge>
+      </Button>
     </>
   );
 }

In addition, the BadgeTheme was updated "default" to be "greyscale". The codemod will insert a temporary _toBadgeTheme helper when the theme is not a string.

 import type { ReactElement } from "react";
-import type { BadgeTheme } from "react-md";
-import { BadgedButton } from "react-md";
+import { Badge, BadgeTheme, Button, getIcon } from "react-md";

 import styles from "./SimpleExamples.module.scss";

@@ -10,15 +9,23 @@ export default function ThemedBadges(): ReactElement {
   return (
     <>
       {themes.map((theme) => (
-        <BadgedButton
+        <Button
           key={theme}
           id={`badged-button-${theme}`}
-          badgeTheme={theme}
           className={styles.container}
+          aria-label="Notifications"
+          buttonType="icon"
         >
-          {theme.length}
-        </BadgedButton>
+          {getIcon("notification")}
+          <Badge theme={_toBadgeTheme(theme)}>{theme.length}</Badge>
+        </Button>
       ))}
     </>
   );
 }
+
+function _toBadgeTheme(
+  theme: BadgeTheme | "default" | undefined,
+): BadgeTheme | undefined {
+  return theme === "default" ? "greyscale" : theme;
+}

Button

✅ remove-unused-props
npx @react-md/codemod v5-to-v6/button/remove-unused-props

The Button no longer supports the ripple props.

 import { Button } from "react-md";
 import styles from "./styles.module.scss";

 export default function Example() {
   return (
     <Button
       onClick={() => {
         // do something
       }}
       disableRipple
-      disableProgrammaticRipple
-      disableEnterClick
-      disableSpacebarClick
-      disablePressedFallback
-      enablePressedAndRipple
-      rippleTimeout={100}
-      rippleClassName={styles.ripple}
-      rippleClassNames={{ enter: "", exit: "" }}
-      rippleContainerClassName="example"
     >
       Hello, world!
     </Button>
   );
 }
✅ rename-button-theme-class-names
npx @react-md/codemod v5-to-v6/button/rename-button-theme-class-names

The buttonThemeClassNames utility function was renamed to button.

 import cn from "classnames";
 import type { ReactElement } from "react";
 import type { ButtonThemeProps, LinkProps } from "react-md";
-import { Link, buttonThemeClassNames } from "react-md";
+import { Link, button } from "react-md";

 import styles from "./WithButtonStyles.module.scss";

@@ -17,7 +17,7 @@ function LinkStyledButton({
   return (
     <Link
       {...props}
-      className={buttonThemeClassNames({
+      className={button({
         disabled,
         theme,
         themeType,

✅ rename-fab
npx @react-md/codemod v5-to-v6/button/rename-fab

The FAB was renamed to FloatingActionButton.

 import { ReactNode, ReactElement } from "react";
-import { FAB, FABProps, FABPosition } from "react-md";
+import {
+  FloatingActionButton,
+  FloatingActionButtonProps,
+  FloatingActionButtonPosition,
+} from "react-md";

-interface CustomProps extends FABProps {
-  altPosition?: FABPosition;
+interface CustomProps extends FloatingActionButtonProps {
+  altPosition?: FloatingActionButtonPosition;
   children: ReactNode;
 }

@@ -10,8 +14,8 @@ export function Custom(props: CustomProps): ReactElement {
   const { children, altPosition, ...remaining } = props;

   return (
-    <FAB {...remaining} position={altPosition}>
+    <FloatingActionButton {...remaining} position={altPosition}>
       {children}
-    </FAB>
+    </FloatingActionButton>
   );
 }
✅ rename-unstyled-button
npx @react-md/codemod v5-to-v6/button/rename-unstyled-button

The UnstyledButton component was renamed to ButtonUnstyled.

@@ -1,4 +1,4 @@
-import { Button, UnstyledButton } from "react-md";
+import { Button, ButtonUnstyled } from "react-md";

 export default function Example() {
   return (
@@ -10,21 +10,21 @@ export default function Example() {
       >
         Hello!
       </Button>
-      <UnstyledButton
+      <ButtonUnstyled
         onClick={() => {
           // do something else
         }}
       >
         Unstyled 1
-      </UnstyledButton>
-      <UnstyledButton
+      </ButtonUnstyled>
+      <ButtonUnstyled
         onClick={() => {
           // do something else
         }}
         className="example-class-name"
       >
         Unstyled 2
-      </UnstyledButton>
+      </ButtonUnstyled>
     </>
   );
 }

Card

npx @react-md/codemod v5-to-v6/card/card-actions-to-card-footer

The CardActions component was renamed to CardFooter and the align prop was renamed to justify.

 import { type ReactElement } from "react";
-import { CardActions } from "react-md";
+import { CardFooter } from "react-md";

 export default function Example(): ReactElement {
   return (
-    <CardActions align="start" className="example">
+    <CardFooter justify="start" className="example">
       Content
-    </CardActions>
+    </CardFooter>
   );
 }
✅ remove-deprecated-card-props
npx @react-md/codemod v5-to-v6/card/remove-deprecated-card-props

The deprecated raiseable prop was removed in favor of raisable.

 export default function Example() {
   return (
     <>
-      <Card raiseable>Content</Card>
+      <Card raisable>Content</Card>
       <Card raisable>Content</Card>
       <Card>Content</Card>
     </>
✅ update-card-content-props
npx @react-md/codemod v5-to-v6/card/update-card-content-props

The CardContent component updated the following props:

   return (
     <>
       <CardContent>Content</CardContent>
-      <CardContent disableParagraphMargin>Content</CardContent>
-      <CardContent disableExtraPadding>Content</CardContent>
+      <CardContent>Content</CardContent>
+      <CardContent disableLastChildPadding>Content</CardContent>
       <CardContent disablePadding disableSecondaryColor className="custom">
         Content
       </CardContent>
✅ update-card-header-props
npx @react-md/codemod v5-to-v6/card/update-card-header-props

The CardHeader updated the following props:

   return (
     <>
       <CardHeader
-        align="top"
-        beforeChildren={<p>Hello</p>}
-        afterChildren={<Typography>World!</Typography>}
-        contentClassName="content-classname"
+        beforeAddon={<p>Hello</p>}
+        afterAddon={<Typography>World!</Typography>}
+        contentProps={{
+          className: "content-classname",
+        }}
       >
         Content
       </CardHeader>
-      <CardHeader contentClassName={styles.className}>Content</CardHeader>
-      <CardHeader contentClassName={cn(styles.className, "another")}>
+      <CardHeader
+        contentProps={{
+          className: styles.className,
+        }}
+      >
+        Content
+      </CardHeader>
+      <CardHeader
+        contentProps={{
+          className: cn(styles.className, "another"),
+        }}
+      >
         Content
       </CardHeader>
     </>
✅ update-card-subtitle-props
npx @react-md/codemod v5-to-v6/card/update-card-subtitle-props

The CardSubtitle updated the following props:

 }: ExampleProps): ReactElement {
   return (
     <>
-      <CardSubtitle noWrap>{children}</CardSubtitle>
-      <CardSubtitle disableSecondaryColor>{children}</CardSubtitle>
+      <CardSubtitle textOverflow="nowrap">{children}</CardSubtitle>
+      <CardSubtitle textColor={null}>{children}</CardSubtitle>
       <CardSubtitle
-        noWrap={noWrap}
-        disableSecondaryColor={disableSecondaryColor}
+        textOverflow={noWrap ? "nowrap" : undefined}
+        textColor={disableSecondaryColor ? null : undefined}
       >
         {children}
       </CardSubtitle>
✅ update-card-title-props
npx @react-md/codemod v5-to-v6/card/update-card-title-props

The CardTitle updated the following props:

   return (
     <>
       <CardTitle>{children}</CardTitle>
-      <CardTitle small>{children}</CardTitle>
-      <CardTitle small={false}>{children}</CardTitle>
-      <CardTitle noWrap>{children}</CardTitle>
-      <CardTitle noWrap={false}>{children}</CardTitle>
-      <CardTitle small noWrap>
+      <CardTitle type="subtitle-1">{children}</CardTitle>
+      <CardTitle>{children}</CardTitle>
+      <CardTitle textOverflow="nowrap">{children}</CardTitle>
+      <CardTitle>{children}</CardTitle>
+      <CardTitle type="subtitle-1" textOverflow="nowrap">
         {children}
       </CardTitle>
     </>

Chip

✅ update-chip-props
npx @react-md/codemod v5-to-v6/chip/update-chip-props

The Chip renamed the noninteractable prop to noninteractive.

 export default function Example(): ReactElement {
   return (
     <>
-      <Chip noninteractable>Hello!</Chip>
+      <Chip noninteractive>Hello!</Chip>
     </>
   );
 }

Dialog

✅ remove-nested-dialog-context-provider
npx @react-md/codemod v5-to-v6/dialog/remove-nested-dialog-context-provider

The NestedDialogContextProvider has been removed and is no longer required.

   IconProvider,
   type MenuConfiguration,
   MenuConfigurationProvider,
-  NestedDialogContextProvider,
   StatesConfig,
   type StatesConfigProps,
   UserInteractionModeListener,
@@ -100,7 +99,7 @@ export function Configuration({
   menuConfiguration,
 }: ConfigurationProps): ReactElement {
   return (
     <Dir defaultDir={defaultDir}>
       <AppSizeListener
         defaultSize={defaultSize}
         onChange={onAppResize}
@@ -110,7 +109,7 @@ export function Configuration({
         desktopMinWidth={desktopMinWidth}
         desktopLargeMinWidth={desktopLargeMinWidth}
       >
-        <NestedDialogContextProvider>
+        <>
           <UserInteractionModeListener>
             <StatesConfig
               disableRipple={disableRipple}
@@ -129,8 +128,8 @@ export function Configuration({
               </HoverModeProvider>
             </StatesConfig>
           </UserInteractionModeListener>
-        </NestedDialogContextProvider>
+        </>
       </AppSizeListener>
     </Dir>
   );
 }
🔧 update-dialog-props
npx @react-md/codemod v5-to-v6/dialog/update-dialog-props

The Dialog and FixedDialog components have the following prop changes:

+// A `Dialog` set the `defaultFocus` but that is no longer supported. Enable the `autoFocus` prop on the target element instead.
 import { ReactElement, useRef } from "react";
 import {
   Button,
   FixedDialog,
   DialogContent,
   DialogFooter,
   DialogHeader,
   DialogTitle,
   Typography,
   useToggle,
 } from "react-md";

 export default function Demo(): ReactElement {
   const [visible, enable, disable] = useToggle(false);
   const fixedTo = useRef<HTMLButtonElement>(null);
   return (
@@ -19,37 +20,31 @@ export default function Demo(): ReactElement {
         Show Dialog
       </Button>
       <FixedDialog
         id="simple-dialog"
         fixedTo={fixedTo}
         visible={visible}
         onRequestClose={disable}
         aria-labelledby="dialog-title"
-        forceContainer
-        defaultFocus="last"
-        disableFocusContainer
-        disableFocusCache
-        disableFocusOnMount
-        disableTabFocusWrap
-        disableFocusOnMountScroll
-        disableFocusOnUnmount
-        unmountFocusFallback
-        disableNestedDialogFixes
-        component="nav"
-        overlay={false}
-        overlayHidden
-        overlayStyle={{ color: "red" }}
-        overlayClassName="custom-overlay-class"
-        containerStyle={{ backgroundColor: "orange" }}
-        containerClassName="custom-container-class"
+        disableOverlay
         options={{ xMargin: 12, yMargin: 12 }}
-        getOptions={() => ({
+        getFixedPositionOptions={() => ({
           vhMargin: 20,
           vwMargin: 20,
         })}
+        overlayProps={{
+          noOpacity: true,
+          style: { color: "red" },
+          className: "custom-overlay-class",
+        }}
+        containerProps={{
+          style: { backgroundColor: "orange" },
+          className: "custom-container-class",
+        }}
+        isFocusTypeDisabled={() => true}
       >
         <DialogHeader>
           <DialogTitle id="dialog-title">Simple Dialog</DialogTitle>
         </DialogHeader>
         <DialogContent>
           <Typography margin="none">This is some text in a dialog.</Typography>
         </DialogContent>
         <DialogFooter>

Divider

🎨 vertical-divider-to-divider
npx @react-md/codemod v5-to-v6/divider/vertical-divider-to-divider

The VerticalDivider component was removed in favor of just enabling the vertical prop on the Divider.

 import { type ReactElement } from "react";
-import { VerticalDivider } from "react-md";
+import { Divider } from "react-md";

 export default function Example(): ReactElement {
   return (
     <>
-      <VerticalDivider />
-      <VerticalDivider maxHeight={0.8} />
-      <VerticalDivider maxHeight={0.8} className="custom-class-name" />
+      <Divider vertical />
+      <Divider vertical />
+      <Divider className="custom-class-name" vertical />
     </>
   );
 }

In addition, the somewhat undocumented useVerticalDividerHeight hook was removed and there is no replacement.

+// TODO: The following react-md imports have been removed from the library and must be manually removed from the rest of the file: VerticalDividerHeight, VerticalDividerHookOptions, useVerticalDividerHeight
 import { type ReactElement } from "react";
-import {
-  VerticalDividerHeight,
-  VerticalDividerHookOptions,
-  useVerticalDividerHeight,
-} from "react-md";

 type H = VerticalDividerHeight;
 type O = VerticalDividerHookOptions;

 export default function Example(): ReactElement {
   const { ref, style } = useVerticalDividerHeight({
     maxHeight: 0.8,
   });

   return <div style={style} ref={ref} vertical />;
 }

Expansion Panel

✅ update-expansion-panel-props
npx @react-md/codemod v5-to-v6/expansion-panel/update-expansion-panel-props

The ExpansionPanel updated the following props:

 export function SimpleExample(): ReactElement {
   return (
     <>
-      <ExpansionPanel raiseable />
-      <ExpansionPanel disablePadding />
-      <ExpansionPanel disableLastParagraphMargin />
-      <ExpansionPanel disableSecondaryColor />
-      <ExpansionPanel disableSecondaryColor={false} />
-      <ExpansionPanel disableSecondaryColor={flag || another} />
+      <ExpansionPanel raisable />
+      <ExpansionPanel disableContentPadding />
+      <ExpansionPanel />
+      <ExpansionPanel />
+      <ExpansionPanel
+        contentProps={{
+          disableSecondaryColor: false,
+        }}
+      />
+      <ExpansionPanel
+        contentProps={{
+          disableSecondaryColor: flag || another,
+        }}
+      />
     </>
   );
 }

 export function HeaderExample(): ReactElement {
   return (
     <>
-      <ExpansionPanel header="Some Content">And some more</ExpansionPanel>
-      <ExpansionPanel header={<Typography>Some JSX</Typography>}>
+      <ExpansionPanel headerChildren="Some Content">
         And some more
       </ExpansionPanel>
-      <ExpansionPanel customHeader={<ExpansionPanelHeader />}>
-        Content
+      <ExpansionPanel headerChildren={<Typography>Some JSX</Typography>}>
+        And some more
       </ExpansionPanel>
+      <ExpansionPanel header={<ExpansionPanelHeader />}>Content</ExpansionPanel>
     </>
   );
 }
🔧 use-panels-to-use-expansion-panel
npx @react-md/codemod v5-to-v6/expansion-panel/use-panels-to-use-expansion-panel

The usePanels hook was renamed to useExpansionPanels with a new API:

@@ -6,7 +6,7 @@ import {
   Fieldset,
   Grid,
   useChecked,
-  usePanels,
+  useExpansionPanels,
 } from "react-md";

 interface Props {
@@ -25,19 +25,19 @@ function Example({
     defaultExpandedIndex = multiple ? [1, 2] : 0;
   }

-  const [panels, onKeyDown] = usePanels({
-    count: 3,
-    idPrefix: "configuring-panels",
+  const { getPanelProps: getPanelProps } = useExpansionPanels({
+    baseId: "configuring-panels",
     multiple,
-    preventAllClosed,
+    preventAllCollapsed: preventAllClosed,
+
     // this will be considered `0` if the `preventAllClosed` option is enabled
     // but still `undefined`
     defaultExpandedIndex,
   });

   return (
-    <ExpansionList onKeyDown={onKeyDown}>
-      <ExpansionPanel {...panels[0]} header="Panel 1">
+    <ExpansionList>
+      <ExpansionPanel {...getPanelProps(0)} header="Panel 1">
         Nam lorem est, porta id tincidunt et, consectetur in nulla. Morbi cursus
         at massa a feugiat. Mauris eu convallis elit, ac mollis metus. Quisque
         pulvinar ante libero, ut laoreet dolor bibendum volutpat. In diam purus,
@@ -46,7 +46,7 @@ function Example({
         ultricies lacus in massa finibus gravida. Maecenas turpis libero,
         fringilla nec sodales sed, lacinia eget libero.
       </ExpansionPanel>
-      <ExpansionPanel {...panels[1]} header="Panel 2">
+      <ExpansionPanel {...getPanelProps(1)} header="Panel 2">
         Aenean rhoncus tristique fringilla. Phasellus ac libero porta, iaculis
         quam quis, porta nibh. Maecenas laoreet dignissim magna quis ultricies.
         Vivamus ut blandit nisl. Curabitur vel turpis vulputate, mollis ante in,
@@ -54,7 +54,7 @@ function Example({
         finibus lectus. Donec eleifend felis odio, vitae gravida purus ornare
         sed.
       </ExpansionPanel>
-      <ExpansionPanel {...panels[2]} header="Panel 3">
+      <ExpansionPanel {...getPanelProps(2)} header="Panel 3">
         Donec lacinia ut sem vitae molestie. Nam placerat tristique facilisis.
         Aliquam iaculis augue eget mollis fermentum. Morbi mattis ultricies
         lacinia. Fusce vitae commodo nisl. Donec congue arcu ut porta feugiat.

Form

🔧 replace-with-message-components
npx @react-md/codemod v5-to-v6/form/replace-with-message-components

The TextFieldWithMessage, TextAreaWithMessage, and PasswordWithMessage components have been removed and instead the default behavior for the TextField, TextArea, and Password components.

 import { ReactElement, useCallback, useMemo, useState } from "react";
 import {
   Button,
   ErrorChangeHandler,
   Form,
   GetErrorMessage,
-  PasswordWithMessage,
-  TextAreaWithMessage,
-  TextFieldWithMessage,
+  Password,
+  TextArea,
+  TextField,
   defaultGetErrorMessage,
   useTextField,
 } from "react-md";

@@ -38,94 +38,90 @@
    return (
     <Form>
-      <TextFieldWithMessage
+      <TextField
         {...nameFieldProps}
         label="Name"
         placeholder="John Doe"
         name="name"
       />
-      <TextFieldWithMessage
+      <TextField
         {...dateFieldProps}
         label="Date *"
         placeholder="01/01/2020"
         name="date"
       />
-      <TextAreaWithMessage
+      <TextArea
         {...descriptionFieldProps}
         label="Description"
         placeholder="Something amazing."
         name="description"
       />
-      <TextAreaWithMessage
+      <TextArea
         {...description2FieldProps}
         label="Description 2 *"
         placeholder="Something amazing."
         name="description2"
       />
-      <PasswordWithMessage
+      <Password
         {...passwordProps}
         label="Password *"
         name="password"
       />
       <Button
         type="submit"
         disabled={errored}
         theme="primary"
         themeType="outline"
       >
         Submit
       </Button>
     </Form>
   );
 }
🔧 update-text-field-container-props
npx @react-md/codemod v5-to-v6/form/update-text-field-container-props

The TextFieldContainer updated a few props which affects the TextField, TextArea, Password, and Select components:

 export default function TextFieldContainerProps(): ReactElement {
   const [value, setValue] = useState("");
   const isLeftAddon = true;
   const isRightAddon = false;
   return (
     <>
       <TextField
         label="Label"
-        leftChildren={<span>Hello!</span>}
-        stretch
-        rightChildren={<span>World!</span>}
-        isLeftAddon
-        isRightAddon={false}
+        leftAddon={<span>Hello!</span>}
+        rightAddon={<span>World!</span>}
+        disableRightAddonStyles
       />
       <TextArea
-        stretch
         label="Label"
-        leftChildren={<span>Hello!</span>}
-        rightChildren={<span>World!</span>}
-        isLeftAddon={isLeftAddon}
-        isRightAddon
+        leftAddon={<span>Hello!</span>}
+        rightAddon={<span>World!</span>}
+        disableLeftAddonStyles={!isLeftAddon}
       />
       <Password
         label="Label"
-        leftChildren={<span>Hello!</span>}
-        rightChildren={<span>World!</span>}
-        stretch
+        leftAddon={<span>Hello!</span>}
+        rightAddon={<span>World!</span>}
        />
       <Select
         id="simple-select-example"
         label="A Label"
         placeholder="Choose..."
         name="select"
         options={states.map(({ abbreviation, name }) => ({
           label: name,
           value: abbreviation,
         }))}
         value={value}
         onChange={(value) => setValue(value)}
-        leftChildren={<span>Hello!</span>}
-        rightChildren={<span>World!</span>}
-        isLeftAddon={false}
-        isRightAddon={false}
-        stretch
+        leftAddon={<span>Hello!</span>}
+        rightAddon={<span>World!</span>}
+        disableLeftAddonStyles
+        disableRightAddonStyles
       />
     </>
   );
 }
🔧 update-use-text-field-api
npx @react-md/codemod v5-to-v6/form/update-use-text-field-api

The useTextField and useNumberField hooks have updated their api slightly:

 import { defaultGetErrorIcon, useNumberField, useTextField } from "react-md";

+export function ExampleDiff(): null {
-  const [, textFieldProps] = useTextField({
+  const { fieldProps: textFieldProps } = useTextField({
     name: "field",
-    validateOnChange: true,
-    getErrorIcon: (errorMessage, error, errorIcon) => {
+    validationType: "change",
+    getErrorIcon: (options) => {
+      const { error, errorIcon, errorMessage } = options;
+
       if (errorMessage === "Hello") {
         return errorIcon;
       }

-      return defaultGetErrorIcon(errorMessage, error, errorIcon);
+      return defaultGetErrorIcon({
+        error,
+        errorIcon,
+        errorMessage,
+      });
     },
   });
-   const [numberValue, numberFieldProps, { reset, setState }] = useNumberField({
-     name: "field2",
-     validateOnChange: false,
-     getErrorIcon: (errorMessage, error, errorIcon) => {
+  const {
+    value: numberValue,
+    fieldProps: numberFieldProps,
+    reset,
+    setState,
+  } = useNumberField({
+    name: "field",
+    validationType: [],
+    getErrorIcon: (options) => {
+      const { error, errorIcon, errorMessage } = options;
+
       if (errorMessage === "Hello") {
         return errorIcon;
       }

-      return defaultGetErrorIcon(errorMessage, error, errorIcon);
+      return defaultGetErrorIcon({
+        error,
+        errorIcon,
+        errorMessage,
+      });
     },
   });

   return null;
 }
❌ update-select-api
npx @react-md/codemod v5-to-v6/form/update-select-api

The Select component has been updated to mimic the native <select> API meaning:

The codemod will attempt to convert the select API, but there were too many edge cases so I gave up. You'll have to manually handle most of the updates.

 import states from "constants/states";
 import { type ReactElement, useState } from "react";
-import { Select } from "react-md";
+import { Option, Select } from "react-md";

 export default function SimpleSelectExample(): ReactElement {
   const [value, setValue] = useState("");
   return (
     <Select
       id="simple-select-example"
       label="A Label"
       placeholder="Choose..."
       name="select"
-      options={states.map(({ abbreviation, name }) => ({
-        label: name,
-        value: abbreviation,
-      }))}
       value={value}
-       onChange={(value) => setValue(value)}
-     />
+      onChange={(event) => {
+        const value = event.currentTarget.value;
+        setValue(value);
+      }}
+    >
+      {states.map(({ abbreviation, name }) => (
+        <Option key={abbreviation} value={abbreviation}>
+          {name}
+        </Option>
+      ))}
+    </Select>
   );
 }
✅ update-file-input-props
npx @react-md/codemod v5-to-v6/form/update-file-input-props

The FileInput removed the disableIconSpacing prop since it is no longer required.

 import { type ReactElement } from "react";
 import { FileInput } from "react-md";

 export default function SimpleFileInput(): ReactElement {
   return (
     <>
       <FileInput />
-      <FileInput disableIconSpacing />
-      <FileInput disableIconSpacing={someFlag} />
-      <FileInput disableIconSpacing={false} />
+      <FileInput />
+      <FileInput />
+      <FileInput />
     </>
   );
 }
❌ update-slider-and-range-slider
npx @react-md/codemod v5-to-v6/form/update-slider-and-range-slider

There were a few breaking changes around the Slider and RangeSlider components:

In addition, the useSlider + useRangeSlider hooks have updated their API to match the new components:

The codemod is only able to handle the simple conversions of renaming baseId -> id and removing unused props. The diff shown below is a "real world" conversion with manual updates.

SimpleSlider.tsx
-import { type ReactElement } from "react";
-import { Slider, useSlider } from "react-md";
+import { type ReactElement, useId } from "react";
+import { Fieldset, Legend, Slider, useSlider } from "react-md";

 export default function SimpleSlider(): ReactElement | null {
-  const [value, controls] = useSlider(20, { min: 0, max: 100 });
-  const { setValue } = controls;
+  const legendId = useId();
+  const slider = useSlider({
+    min: 0,
+    max: 100,
+    defaultValue: 20,
+  });
+  const { value, setValue } = slider;

   return (
-    <>
-      <Slider baseId="slider-1-id" label="Horizontal" {...controls} />
-    </>
+    <Fieldset>
+      <Legend id={legendId}>Horizontal</Legend>
+      <Slider id="slider-1-id" {...slider} aria-labelledby={legendId} />
+    </Fieldset>
   );
 }
SimpleRangeSlider.tsx
-import { type ReactElement } from "react";
-import { RangeSlider, useRangeSlider } from "react-md";
+import { type ReactElement, useId } from "react";
+import { Fieldset, Legend, Slider, useRangeSlider } from "react-md";

 export default function SimpleSlider(): ReactElement | null {
-  const [[minValue, maxValue], controls] = useRangeSlider([20, 60], { min: 0, max: 100 });
-  const { setValue } = controls;
+  const legendId = useId();
+  const rangeSlider = useRangeSlider({
+    min: 0,
+    max: 100,
+    defaultValue: [20, 60],
+  });
+  const { rangeValue, setRangeValue: setValue } = rangeSlider;
+  const [minValue, maxValue] = rangeValue;

   return (
-    <>
-      <Slider baseId="slider-1-id" label="Horizontal" {...controls} />
-    </>
+    <Fieldset>
+      <Legend id={legendId}>Horizontal</Legend>
+      <Slider id="slider-1-id" {...rangeSlider} aria-labelledby={legendId} />
+    </Fieldset>
   );
 }
🔧 update-password-props
npx @react-md/codemod v5-to-v6/form/update-file-input-props

The Password component has the following breaking changes:

+// TODO: Update `GetPasswordVisibilityIcon` references since it provides the `type === "password"` flag instead of the `type` now
 import { type ReactElement } from "react";
-import { GetVisibilityIcon, Password } from "react-md";
+import { GetPasswordVisibilityIcon, Password } from "react-md";

-const getVisibilityIcon: GetVisibilityIcon = (type) =>
-  type === "password" ? <span id="password-icon" /> : <span id="text-icon" />;
+// this is not auto-converted
+const getVisibilityIcon: GetPasswordVisibilityIcon = (isPassword) =>
+  isPassword ? <span id="password-icon" /> : <span id="text-icon" />;

 export default function PasswordExample(): ReactElement {
   return (
     <>
       <Password
         id="example-password-field"
         label="Password"
         placeholder="Super secret password"
       />
       <Password
         id="example-password-field-2"
         label="Password"
         placeholder="Super secret password"
-        visibilityStyle={{ opacity: 0.5 }}
-        visibilityClassName="visibility-class-name"
-        onVisibilityClick={(event) => {
-          // do something
-        }}
-      />
+        visibilityProps={{
+          style: { opacity: 0.5 },
+          className: "visibility-class-name",
+
+          onClick: (event) => {
+            // do something
+          }
+        }} />
       <Password
         id="example-password-field-2"
         label="Password"
         placeholder="Super secret password"
-        getVisibilityIcon={getVisibilityIcon}
+        visibilityIcon={getVisibilityIcon}
       />
     </>
   );
 }
❌ Update Radio and Checkbox Icons

The Radio and Checkbox components no longer use a single icon and hackily overlay the icon to show the checked/unchecked states.

Layout ❌

There is no longer a pre-built layout component so see the new Layout component documentation for the new setup. For convenience, here's a list of the changes:

Here's an example diff for converting the Configurable Layout Example from the v5 website.

A custom renderer must be passed to the Tree component if there are non-tree items in the navigation tree like dividers or subheaders.

+import { AppBar } from "@react-md/core/app-bar/AppBar";
+import { AppBarTitle } from "@react-md/core/app-bar/AppBarTitle";
+import { Button } from "@react-md/core/button/Button";
+import { LayoutNav } from "@react-md/core/layout/LayoutNav";
+import { Main } from "@react-md/core/layout/Main";
+import { useExpandableLayout } from "@react-md/core/layout/useExpandableLayout";
+import { useLayoutTree } from "@react-md/core/layout/useLayoutTree";
+import { Sheet } from "@react-md/core/sheet/Sheet";
+import { Tree } from "@react-md/core/tree/Tree";
+import { type TreeData } from "@react-md/core/tree/types";
import {
  HomeSVGIcon,
  SecuritySVGIcon,
  SettingsSVGIcon,
  ShareSVGIcon,
  SnoozeSVGIcon,
  StarSVGIcon,
  StorageSVGIcon,
} from "@react-md/material-icons";
import { type ReactElement, useState } from "react";
-import {
-  DEFAULT_DESKTOP_LAYOUT,
-  DEFAULT_LANDSCAPE_TABLET_LAYOUT,
-  DEFAULT_PHONE_LAYOUT,
-  DEFAULT_TABLET_LAYOUT,
-  Layout,
-  type LayoutNavigationTree,
-  type SupportedPhoneLayout,
-  type SupportedTabletLayout,
-  type SupportedWideLayout,
-  useLayoutNavigation,
-} from "react-md";

-import { ConfigurationForm } from "./ConfigurationForm";
import { CurrentChildren } from "./CurrentChildren";

-const navItems: LayoutNavigationTree = {
+const navItems: TreeData = {
  "/": {
    itemId: "/",
+    href: "/",
    parentId: null,
    children: "Home",
    leftAddon: <HomeSVGIcon />,
  },
  "/route-1": {
    itemId: "/route-1",
+    href: "/route-1",
    parentId: null,
    children: "Route 1",
    leftAddon: <StarSVGIcon />,
  },
  "/divider-1": {
    itemId: "/divider-1",
    parentId: null,
    divider: true,
    isCustom: true,
  },
  "/route-2": {
    itemId: "/route-2",
+    href: "/route-2",
    parentId: null,
    children: "Route 2",
    leftAddon: <ShareSVGIcon />,
  },
  "/route-2-1": {
    itemId: "/route-2-1",
+    href: "/route-2-1",
    parentId: "/route-2",
    children: "Route 2-1",
    leftAddon: <SettingsSVGIcon />,
  },
  "/route-2-2": {
    itemId: "/route-2-2",
+    href: "/route-2-2",
    parentId: "/route-2",
    children: "Route 2-2",
    leftAddon: <StorageSVGIcon />,
  },
  "/route-2-3": {
    itemId: "/route-2-3",
+    href: "/route-2-3",
    parentId: "/route-2",
    children: "Route 2-3",
    leftAddon: <SecuritySVGIcon />,
  },
  "/route-3": {
    itemId: "/route-3",
+    href: "/route-3",
    parentId: null,
    children: "Route 3",
    leftAddon: <SnoozeSVGIcon />,
  },
  "/route-4": {
    itemId: "/route-4",
+    href: "/route-4",
    parentId: null,
    children: "Route 4",
  },
};

export default function Demo(): ReactElement {
-  const [phoneLayout, setPhoneLayout] =
-    useState<SupportedPhoneLayout>(DEFAULT_PHONE_LAYOUT);
-  const [tabletLayout, setTabletLayout] = useState<SupportedTabletLayout>(
-    DEFAULT_TABLET_LAYOUT
-  );
-  const [landscapeTabletLayout, setLandscapeTabletLayout] =
-    useState<SupportedTabletLayout>(DEFAULT_LANDSCAPE_TABLET_LAYOUT);
-  const [desktopLayout, setDesktopLayout] = useState<SupportedWideLayout>(
-    DEFAULT_DESKTOP_LAYOUT
-  );
-  const [largeDesktopLayout, setLargeDesktopLayout] =
-    useState<SupportedWideLayout>(DEFAULT_DESKTOP_LAYOUT);
-
  const [selectedId, setSelectedId] = useState("/");
+  const tree = useLayoutTree({
+    navItems,
+    pathname: selectedId,
+  });
+  const {
+    temporary,
+    persistent,
+    temporaryNavProps,
+    navToggleProps,
+    appBarProps,
+    mainProps,
+    expandableNavProps,
+  } = useExpandableLayout({
+    pathname: selectedId,
+    fullHeightNav: "static",
+  });

   return (
-    <Layout
-      id="configurable-layout"
-      title="Configurable Layout Title"
-      navHeaderTitle="Another Title"
-      phoneLayout={phoneLayout}
-      tabletLayout={tabletLayout}
-      landscapeTabletLayout={landscapeTabletLayout}
-      desktopLayout={desktopLayout}
-      treeProps={{
-        ...useLayoutNavigation(navItems, selectedId),
-        onItemSelect: setSelectedId,
-      }}
-      // this is only required since I already have a main element due to the
-      // documentation site's Layout component
-      mainProps={{ component: "div" }}
-    >
-      <CurrentChildren route={selectedId} />
-      <ConfigurationForm
-        phoneLayout={phoneLayout}
-        setPhoneLayout={setPhoneLayout}
-        tabletLayout={tabletLayout}
-        setTabletLayout={setTabletLayout}
-        landscapeTabletLayout={landscapeTabletLayout}
-        setLandscapeTabletLayout={setLandscapeTabletLayout}
-        desktopLayout={desktopLayout}
-        setDesktopLayout={setDesktopLayout}
-        largeDesktopLayout={largeDesktopLayout}
-        setLargeDesktopLayout={setLargeDesktopLayout}
-      />
-    </Layout>
+    <>
+      <AppBar {...appBarProps}>
+        <Button {...navToggleProps} />
+        <AppBarTitle>Configurable Layout Title</AppBarTitle>
+      </AppBar>
+      {persistent && (
+        <LayoutNav {...expandableNavProps}>
+          <AppBar>
+            <AppBarTitle>Another Title</AppBarTitle>
+          </AppBar>
+          <Tree
+            {...tree}
+            aria-label="Navigation"
+            toggleTreeItemSelection={setSelectedId}
+          />
+        </LayoutNav>
+      )}
+      {temporary && (
+        <Sheet {...temporaryNavProps}>
+          <Tree
+            {...tree}
+            aria-label="Navigation"
+            toggleTreeItemSelection={setSelectedId}
+          />
+        </Sheet>
+      )}
+      <Main {...mainProps}>
+        <CurrentChildren route={selectedId} />
+      </Main>
+    </>
   );
 }
npx @react-md/codemod v5-to-v6/link/update-link-props

The Link component updated the following props:

   return (
     <>
       <Link href="#">Link 1</Link>
-      <Link href="#" flexCentered>
+      <Link href="#" flex>
         Link 1
       </Link>
-      <Link href="#" flexCentered={flexCentered}>
-        Link 1
-      </Link>
-      <Link href="#" preventMaliciousTarget={false}>
+      <Link href="#" flex={flexCentered}>
         Link 1
       </Link>
+      <Link href="#">Link 1</Link>
     </>
   );
 }

List

There are a few breaking changes for the list components:

For Typescript users, the ListItemLink no longer allows { [key: string]: unknown } when the as component is provided.

🔧 update-list-item-props
npx @react-md/codemod v5-to-v6/list/update-list-item-props

Update textChildren prop:

 import { type ReactElement } from "react";
 import { ListItem } from "react-md";

 export default function Example(): ReactElement {
   return (
     <>
-      <ListItem textChildren>Hello, world!</ListItem>
-      <ListItem textChildren={false}>Hello, world!</ListItem>
-      <ListItem textChildren={true}>Hello, world!</ListItem>
-      <ListItem textChildren={textChildren}>Hello, world!</ListItem>
+      <ListItem>Hello, world!</ListItem>
+      <ListItem disableTextChildren>Hello, world!</ListItem>
+      <ListItem>Hello, world!</ListItem>
+      <ListItem disableTextChildren={!textChildren}>Hello, world!</ListItem>
     </>
   );
 }

Remove ripple props:

 import { type ReactElement } from "react";
 import { ListItem } from "react-md";
 import styles from "./styles.module.scss";

 export default function Example(): ReactElement {
   return (
     <ListItem
       onClick={() => {
         // do something
       }}
       disableRipple
-      disableProgrammaticRipple
-      disableEnterClick
-      disableSpacebarClick
-      disablePressedFallback
-      enablePressedAndRipple
-      rippleTimeout={100}
-      rippleClassName={styles.ripple}
-      rippleClassNames={{ enter: "", exit: "" }}
-      rippleContainerClassName="example"
     >
       Hello, world!
     </ListItem>
   );
 }

Rename component to as:

 export default function Example(): ReactElement {
   return (
     <>
-      <ListItemLink component={CustomLink} to="/some-path">
+      <ListItemLink as={CustomLink} to="/some-path">
         Link 1
       </ListItemLink>
-      <ListItemLink component="a" href="/some-path">
+      <ListItemLink as="a" href="/some-path">
         Link 2
       </ListItemLink>
     </>

Remove SimpleListItem:

 import type { ReactElement } from "react";
 import cn from "classnames";
-import { FavoriteSVGIcon, List, SimpleListItem } from "react-md";
+import { FavoriteSVGIcon, List, ListItemChildren } from "react-md";

 import people from "./people";

@@ -12,33 +12,35 @@ export default function Demo(): ReactElement {
     <Container>
       <List>
         {people.slice(0, 10).map((name) => (
-          <SimpleListItem
+          <li
             key={name}
             className={cn(styles.item, styles.dotted, styles.margin)}
           >
-            {name}
-          </SimpleListItem>
+            <ListItemChildren>{name}</ListItemChildren>
+          </li>
         ))}
       </List>
       <List className={styles.ordered}>
         {people.slice(11, 20).map((name) => (
-          <SimpleListItem key={name} className={cn(styles.item, styles.margin)}>
-            {name}
-          </SimpleListItem>
+          <li key={name} className={cn(styles.item, styles.margin)}>
+            <ListItemChildren>{name}</ListItemChildren>
+          </li>
         ))}
       </List>
       <List>
-        <SimpleListItem
-          primaryText={<span>Primary Text</span>}
-          secondaryText={
-            <span className={styles.secondary}>Secondary Text</span>
-          }
-          leftAddon={<FavoriteSVGIcon />}
-          rightAddon={<img alt="" src="https://example.com/image.jpeg" />}
-          rightAddonType="media"
-        >
-          Other children
-        </SimpleListItem>
+        <li>
+          <ListItemChildren
+            primaryText={<span>Primary Text</span>}
+            secondaryText={
+              <span className={styles.secondary}>Secondary Text</span>
+            }
+            leftAddon={<FavoriteSVGIcon />}
+            rightAddon={<img alt="" src="https://example.com/image.jpeg" />}
+            rightAddonType="media"
+          >
+            Other children
+          </ListItemChildren>
+        </li>
       </List>
     </Container>
   );

Media

There are a few breaking changes for the media components since the new objectFit class name utility handles most use-cases now.

🔧 update-media-components
npx @react-md/codemod v5-to-v6/media/update-media-components
 import type { ReactElement } from "react";
-import { MediaContainer, MediaOverlay, Typography, type MediaOverlayPosition } from "react-md";
+import { ResponsiveItem, ResponsiveItemOverlay, Typography, ResponsiveItemOverlayPosition } from "react-md";

 import styles from "./Container.module.scss";

-const positions: MediaOverlayPosition[] = [
+const positions: ResponsiveItemOverlayPosition[] = [
   "top",
   "right",
   "bottom",
   "left",
   "middle",
   "center",
   "absolute-center",
 ];

 export default function WithOverlay(): ReactElement {
   return (
     <>
       {positions.map((position, i) => (
-        <MediaContainer
+        <ResponsiveItem
           key={position}
           id={`overlay-container-${i}`}
-          height={9}
-          width={16}
           className={styles.container}
-        >
+          aspectRatio="16-9">
           <img
             src={`https://picsum.photos/800/800?image=43${i}`}
             alt=""
             aria-describedby={`overlay-container-overlay-${i}`}
           />
-          <MediaOverlay
+          <ResponsiveItemOverlay
             id={`overlay-container-overlay-${i}`}
             position={position}
           >
             <Typography
               type="headline-5"
               margin="none"
               align={
                 ["left", "right", "center"].includes(position)
                   ? "center"
                   : undefined
               }
             >
               This is a random picture!
             </Typography>
-          </MediaOverlay>
-        </MediaContainer>
+          </ResponsiveItemOverlay>
+        </ResponsiveItem>
       ))}
     </>
   );
 }

The majority of the menu component changes were internal, but there are a few breaking changes that can mostly be automated through the codemod.

npx @react-md/codemod v5-to-v6/menu/replace-menu-item-link

The MenuItemLink was removed in favor of just using a ListItemLink with role="menuitem". All the other transforms from update-list-item-props must then be applied as well.

This codemod will convert to a ListItemLink and then handle the ListItemLink migration.

 import type { ReactElement } from "react";
-import { DropdownMenu, MenuItemLink, Link } from "react-md";
+import { DropdownMenu, Link, ListItemLink } from "react-md";

 export default function SimpleExample(): ReactElement {
   return (
     <DropdownMenu buttonChildren="Options...">
-      <MenuItemLink href="/link-1" component={Link}>
+      <ListItemLink href="/link-1" as={Link} role="menuitem">
         Link 1
-      </MenuItemLink>
-      <MenuItemLink
+      </ListItemLink>
+      <ListItemLink
         href="#link-2"
         liProps={{ className: "container-class-name" }}
         className="link-class-name"
-      >
+        role="menuitem">
         Link 2
-      </MenuItemLink>
+      </ListItemLink>
     </DropdownMenu>
   );
 }
🔧 update-use-context-menu-api
npx @react-md/codemod v5-to-v6/menu/update-use-context-menu-api

The useContextMenu hook was updated for the latest Menu API and only supports the following options: anchor, menuLabel, preventScroll, and onContextMenu.

In addition, the hook only returns: visible, setVisible, menuProps, and onContextMenu. The onContextMenu type definition has also been updated to better support triggering the context menu from different element types.

Here is an example diff of the most common changes:

 import ContentCopyIcon from "@react-md/material-icons/ContentCopyIcon";
 import ContentCutIcon from "@react-md/material-icons/ContentCutIcon";
 import ContentPasteIcon from "@react-md/material-icons/ContentPasteIcon";
 import type { ReactElement } from "react";
 import { Menu, MenuItem, TextArea, useContextMenu } from "react-md";

 export default function SimpleContextMenu(): ReactElement {
-  const { menuRef, menuProps, onContextMenu } = useContextMenu();
+  const {
+    menuProps,
+    onContextMenu
+  } = useContextMenu();
   return (
     <>
       <TextArea
         id="simple-context-menu-area"
         onContextMenu={onContextMenu}
         placeholder="Right click me!"
       />
-      <Menu {...menuProps} ref={menuRef}>
+      <Menu {...menuProps}>
         <MenuItem leftAddon={<ContentCutIcon />}>Cut</MenuItem>
         <MenuItem leftAddon={<ContentCopyIcon />}>Copy</MenuItem>
         <MenuItem leftAddon={<ContentPasteIcon />}>Paste</MenuItem>
       </Menu>
     </>
   );
 }


Here is an example diff of all the changes and what the codemod can support:

+// TODO: The `useContextMenu` no longer supports the `onFixedPositionScroll` callback
+// TODO: The `useContextMenu` no longer supports the `onFixedPositionResize` callback
+// TODO: The `useContextMenu` no longer returns `setCoords`. Manually provide an `initialX` and `initialY` to the `Menu` instead
+// TODO: The `useContextMenu` no longer returns `menuNodeRef` and must be implemented manually
 import ContentCopyIcon from "@react-md/material-icons/ContentCopyIcon";
 import ContentCutIcon from "@react-md/material-icons/ContentCutIcon";
 import ContentPasteIcon from "@react-md/material-icons/ContentPasteIcon";
 import type { ReactElement } from "react";
 import { Menu, MenuItem, TextArea, TOP_RIGHT_ANCHOR, useContextMenu } from "react-md";

 export default function ComplexContextMenu(): ReactElement {
   const onEntering = (): void => {
     // entering
   };
   const {
-    menuRef,
-    menuNodeRef,
     menuProps,
     onContextMenu,
-    setCoords,
     visible,
-    setVisible,
+    setVisible
   } = useContextMenu({
     anchor: TOP_RIGHT_ANCHOR,
-    baseId: "context-menu",
-    closeOnResize: true,
-    closeOnScroll: true,
-    disableFocusOnMount: true,
-    disableFocusOnUnmount: true,
-    getFixedPositionOptions: () => ({}),
-    horizontal: false,
     menuLabel: "Context Menu",
-    menuitem: false,
+
     onContextMenu(event) {
       // onContextMenu
     },
-    onEnter() {
-      // enter
-    },
-    onEntering,
-    onEntered: () => {
-      // entered
-    },
-    onExited: () => {
-      // exited
-    },
-    onFixedPositionResize: (event) => {
-      // resized
-    },
-    onFixedPositionScroll: (event, data) => {
-      // scroll
-    },
-    preventScroll: true,
-    style: {
-      background: "orange",
-    },
+
+    preventScroll: true
   });

   return (
     <>
       <TextArea
         id="simple-context-menu-area"
         onContextMenu={onContextMenu}
         placeholder="Right click me!"
       />
-      <Menu {...menuProps} ref={menuRef}>
+      <Menu
+        {...menuProps}
+        id="context-menu"
+        closeOnResize={true}
+        closeOnScroll={true}
+        getFixedPositionOptions={() => ({})}
+        horizontal={false}
+        onEnter={() => {
+          // enter
+        }}
+        onEntering={onEntering}
+        onEntered={() => {
+          // entered
+        }}
+        onExited={() => {
+          // exited
+        }}
+        style={{
+          background: "orange"
+        }}
+        isFocusTypeDisabled={type => {
+          if (type === "mount") {
+            return true;
+          }
+
+          if (type === "unmount") {
+            return true;
+          }
+
+          return false;
+        }}>
         <MenuItem leftAddon={<ContentCutIcon />}>Cut</MenuItem>
         <MenuItem leftAddon={<ContentCopyIcon />}>Copy</MenuItem>
         <MenuItem leftAddon={<ContentPasteIcon />}>Paste</MenuItem>
       </Menu>
     </>
   );
 }

Overlay

🔧 update-overlay-props
npx @react-md/codemod v5-to-v6/overlay/update-overlay-props

The Overlay updated the following props:

     <>
       <Overlay
         visible={visible}
-        onRequestClose={() => {
+        onClick={() => {
           setVisible(false);
         }}
       />
-      <Overlay visible={visible2} onRequestClose={hide} />
+      <Overlay visible={visible2} onClick={hide} />
       <Overlay
         visible={visible2}
-        onRequestClose={() => {
+        onClick={() => {
           hide();
         }}
-        hidden
+        noOpacity
       />
       <Overlay
         visible={visible}
-        onRequestClose={() => {
+        onClick={() => {
           setVisible(false);
         }}
         clickable
@@ -39,7 +39,7 @@ export default function Example({ handleClick }: ExampleProps): ReactElement {
         onClick={(event) => {
           handleClick?.(event);
         }}
-        onRequestClose={() => {
+        onClick={() => {
           setVisible(false);
         }}
         clickable

Portal

🔧 use-new-portal-api
npx @react-md/codemod v5-to-v6/portal/use-new-portal-api

Most of the Portal changes cannot be automated due to the new portal container behavior, but should not affect most codebases since it is mostly an internal component. The changes are:

+// TODO: Check how the Portal prop `intoId` was used since it is no longer supported.
+// TODO: Check how the Portal prop `into` was used since it is no longer supported.
 import { type ReactElement } from "react";
 import { Portal } from "react-md";

 export default function Example(): ReactElement {
   return (
     <>
-      <Portal into={something}>Content</Portal>
-      <Portal intoId="some-id">Content</Portal>
-      <Portal into={somethingElse}>Content</Portal>
-      <Portal intoId="some-other-id">Content</Portal>
+      <Portal>Content</Portal>
+      <Portal>Content</Portal>
+      <Portal>Content</Portal>
+      <Portal>Content</Portal>
     </>
   );
 }
-import { PortalInto } from "react-md";
+// TODO: The `PortalContainer` replaced the `PortalInto` type but does not support functions. Double check the usage before removing this line.
+import { PortalContainer } from "react-md";

 export interface ExampleProps {
-  portalInto: PortalInto;
+  portalInto: PortalContainer;
 }
 import { type ReactElement } from "react";
-import { ConditionalPortal } from "react-md";
+import { Portal } from "react-md";

 export default function Example(): ReactElement {
   return (
     <>
-      <ConditionalPortal portal={false}>Content</ConditionalPortal>
-      <ConditionalPortal>Hello, world!</ConditionalPortal>
-      <ConditionalPortal>
+      <Portal disabled>Content</Portal>
+      <Portal disabled>Hello, world!</Portal>
+      <Portal disabled>
         <div>Hello, world!</div>
-      </ConditionalPortal>
+      </Portal>
     </>
   );
 }

In addition, the ConditionalPortal was removed.

Progress

✅ update-circular-progress-props
npx @react-md/codemod v5-to-v6/progress/update-circular-progress-props

The CircularProgress updated the following props:

 export default function Example(): ReactElement {
   return (
     <>
-      <CircularProgress id="some-id-1" small />
-      <CircularProgress id="some-id-2" small={false} />
-      <CircularProgress id="some-id-3" centered />
-      <CircularProgress id="some-id-4" centered={false} />
-      <CircularProgress id="some-id-5" maxRotation={360 * 1.5} />
+      <CircularProgress id="some-id-1" dense />
+      <CircularProgress id="some-id-2" />
+      <CircularProgress id="some-id-3" />
+      <CircularProgress id="some-id-4" disableCentered />
+      <CircularProgress id="some-id-5" />
     </>
   );
 }

Sheet 🎉

States ❌

The @react-md/states packages was mostly internal so codemods will not be provided to update this package.

Replace StatesConnfig context provider

The StatesConfig context provider has been removed in favor of the new INTERACTION_CONFIG object. Here is an example on how to update the disableRipple behavior:

+import { INTERACTION_CONFIG } from "@react-md/core/interaction/config";
-import { StatesConfig } from "react-md";
 import { RestOfTheApp } from "./RestOfTheApp.jsx";

+INTERACTION_CONFIG.mode = "none";

 export default function App() {
   return (
-    <StatesConfig disableRipple>
       <RestOfTheApp />
-    </StatesConfig>
   );
 }
Replace useInteractionStates with useElementInteraction

If the useInteractionStates hook was used, switch to the useElementInteraction hook.

 import cn from "classnames";
 import { type ButtonHTMLAttributes, type ReactElement } from "react";
-import { type InteractionStatesOptions, useInteractionStates } from "react-md";
+import { useElementInteraction } from "react-md";

 import styles from "./CustomComponent.module.scss";

-interface CustomButtonProps
-  extends ButtonHTMLAttributes<HTMLButtonElement>,
-    Omit<InteractionStatesOptions, "handlers"> {}
+interface CustomButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
+  disableRipple?: boolean;
+}

 function CustomButton({
   disabled,
   disableRipple,
-  disableSpacebarClick,
-  disableProgrammaticRipple,
-  className: propClassName,
+  className,
   children,
+  onBlur,
+  onClick,
   onKeyDown,
   onKeyUp,
   onMouseDown,
   onMouseUp,
   onMouseLeave,
+  onDragStart,
   onTouchStart,
   onTouchMove,
   onTouchEnd,
   ...props
 }: CustomButtonProps): ReactElement {
-  const { ripples, handlers, className } = useInteractionStates({
-    handlers: {
+  const { ripples, handlers, pressed, pressedClassName } =
+    useElementInteraction({
+      onBlur,
+      onClick,
       onKeyDown,
       onKeyUp,
       onMouseDown,
       onMouseUp,
       onMouseLeave,
+      onDragStart,
       onTouchStart,
       onTouchMove,
       onTouchEnd,
-    },
-    disabled,
-    disableRipple,
-    disableSpacebarClick,
-    disableProgrammaticRipple,
-    className: propClassName,
-  });
+      disabled,
+      mode: disableRipple ? "none" : undefined,
+    });

   return (
     <button
       {...props}
       type="button"
-      className={cn(styles.button, className)}
+      className={cn(
+        styles.button,
+        className,
+        pressedClassName,
+        pressed && styles.pressed
+      )}
       {...handlers}
     >
       {children}
       {ripples}
     </button>
   );
 }

Table

🔧 update-table-cell-props
npx @react-md/codemod v5-to-v6/table/update-table-cell-props

The TableCell updated the following props:

+// TODO: Update the `TableCell` to have a `colSpan` number equal to the total number of columns in the table
 import type { ReactElement } from "react";
 import {
   Table,
@@ -33,7 +34,7 @@ export default function StickyColumnsPart3(): ReactElement {
         </TableBody>
         <TableFooter sticky>
           <TableRow>
-            <TableCell colSpan="100%">
+            <TableCell>
               This is the sticky footer content. Any components can be rendered
               inside.
             </TableCell>
-            <TableCell disablePadding className="example">
+            <TableCell padding="none" className="example">
               Content
             </TableCell>
🔧 update-table-checkbox-props
npx @react-md/codemod v5-to-v6/table/update-table-checkbox-props

The TableCheckbox no longer supports the cellId prop in favor of the id prop.

 export function Example(props): ReactElement {
   return (
     <>
-      <TableCheckbox {...props} cellId="some-unique-id" />
+      <TableCheckbox {...props} />
     </>
   );
 }
✅ caption-to-typography
npx @react-md/codemod v5-to-v6/table/caption-to-typography

The Caption component was removed in favor of using the existing Typography component.

@@ -1,11 +1,11 @@
 import type { ReactElement } from "react";
 import {
-  Caption,
   Table,
   TableBody,
   TableCell,
   TableHeader,
   TableRow,
+  Typography,
 } from "react-md";

 import styles from "./DefaultStyles.module.scss";
@@ -13,7 +13,7 @@ import styles from "./DefaultStyles.module.scss";
 export default function DefaultStyles(): ReactElement {
   return (
     <Table className={styles.centered}>
-      <Caption>This is a caption</Caption>
+      <Typography type="caption">This is a caption</Typography>
       <TableHeader>
         <TableRow>
           <TableCell>Column 1</TableCell>

Tabs

There is a completely new tabs API which will require manual changes. First, the TabsManager and Tabs components have been removed and should be replaced by:

The TabPanels and TabPanel components have been removed in favor of the getTabPanelsProps and getTabPanelProps returned by the useTabs hook. The previous slide transition can be implemented using the SlideContainer and Slide components.

The Slide component behaves like the TabPanel with the persistent flag enabled. If the tab panels should be unmounted while not active, enable the temporary flag on the Slide.

To migrate to the new API, the step are as follows:

  import type { ReactElement } from "react";
-import { TabPanel, TabPanels, Tabs, TabsManager, Typography } from "react-md";
+import {
+  Slide,
+  SlideContainer,
+  Tab,
+  TabList,
+  Typography,
+  useTabs,
+} from "react-md";

 const tabs = ["Tab 1", "Tab 2", "Tab 3"];
 const stacked = false;
 const iconAfter = false;
 const defaultActiveIndex = 0;

 export default function Demo(): ReactElement {
+  const { getTabProps, getTabListProps, getTabPanelProps, getTabPanelsProps } =
+    useTabs({
+      baseId: "basic-usage-tabs",
+      stacked: stacked,
+      iconAfter: iconAfter,
+      defaultActiveIndex: defaultActiveIndex,
+    });
+
   return (
-    <TabsManager
-      tabs={tabs}
-      tabsId="basic-usage-tabs"
-      stacked={stacked}
-      iconAfter={iconAfter}
-      defaultActiveIndex={defaultActiveIndex}
-    >
-      <Tabs />
-      <TabPanels>
-        <TabPanel>
+    <>
+      <TabList {...getTabListProps()}>
+        {tabs.map((tab, i) => (
+          <Tab {...getTabProps(i)}>{tab}</Tab>
+        ))}
+      </TabList>
+      <SlideContainer {...getTabPanelsProps()}>
+        <Slide {...getTabPanelProps(0)}>
           <Typography type="headline-4">Panel 1</Typography>
-        </TabPanel>
-        <TabPanel>
+        </Slide>
+        <Slide {...getTabPanelProps(1)}>
           <Typography type="headline-4">Panel 2</Typography>
-        </TabPanel>
-        <TabPanel>
+        </Slide>
+        <Slide {...getTabPanelProps(2)}>
           <Typography type="headline-4">Panel 3</Typography>
-        </TabPanel>
-      </TabPanels>
-    </TabsManager>
+        </Slide>
+      </SlideContainer>
+    </>
   );
 }

❌ update-tabs-api
npx @react-md/codemod v5-to-v6/tabs/update-tabs-api

This codemod will try its best to convert tabs to the latest API but will really only work if the TabsManager, Tabs, TabPanels, and TabPanel components were defined in the same file.

-import type { ReactElement } from "react";
-import { TabPanel, TabPanels, Tabs, TabsManager, Typography } from "react-md";
+import { ReactElement, ReactNode } from "react";
+import {
+  Slide,
+  SlideContainer,
+  Tab,
+  TabList,
+  TabProps,
+  Typography,
+  useTabs,
+} from "react-md";

 const tabs = ["Tab 1", "Tab 2", "Tab 3"];

 export default function Demo(): ReactElement {
+  const { getTabProps, getTabListProps, getTabPanelProps, getTabPanelsProps } =
+    useTabs({
+      baseId: "basic-usage-tabs",
+    });
+
   return (
-    <TabsManager tabs={tabs} tabsId="basic-usage-tabs">
-      <Tabs />
-      <TabPanels>
-        <TabPanel>
+    <>
+      <TabList {...getTabListProps()}>
+        {tabs.map((tab, i) => {
+          const tabProps = getTabProps(i);
+          let children: ReactNode;
+          let overrides: TabProps | undefined;
+
+          if (typeof tab !== "string" && "children" in tab) {
+            children = tab.children;
+
+            const {
+              children: _c,
+              contentStyle: _cs,
+              contentClassName: _ccn,
+              ...stillValidProps
+            } = tab;
+
+            overrides = stillValidProps;
+          } else {
+            children = tab;
+          }
+
+          return (
+            <Tab {...tabProps} {...overrides} key={tabProps.id}>
+              {children}
+            </Tab>
+          );
+        })}
+      </TabList>
+      <SlideContainer {...getTabPanelsProps()}>
+        <Slide {...getTabPanelProps(0)}>
           <Typography type="headline-4">Panel 1</Typography>
-        </TabPanel>
-        <TabPanel>
+        </Slide>
+        <Slide {...getTabPanelProps(1)}>
           <Typography type="headline-4">Panel 2</Typography>
-        </TabPanel>
-        <TabPanel>
+        </Slide>
+        <Slide {...getTabPanelProps(2)}>
           <Typography type="headline-4">Panel 3</Typography>
-        </TabPanel>
-      </TabPanels>
-    </TabsManager>
+        </Slide>
+      </SlideContainer>
+    </>
   );
 }

Transition

🔧 update-scale-transition
npx @react-md/codemod v5-to-v6/transition/update-scale-transition

The ScaleTransition component no longer supports the portal, portalInto, and portalIntoId props.

+// TODO: The `portalInto` for the `ScaleTransition` cannot be converted automatically.
+// TODO: The `portalIntoId` for the `ScaleTransition` cannot be converted automatically.
 import { type ReactElement } from "react";
-import { ScaleTransition } from "react-md";
+import { Portal, ScaleTransition } from "react-md";
 import SomeComponent from "./SomeComponent";

 export default function Example(): ReactElement {
   return (
     <>
-      <ScaleTransition portalIntoId="some-id" transitionIn={transitionIn}>
-        <SomeComponent />
-      </ScaleTransition>
+      <Portal>
+        <ScaleTransition transitionIn={transitionIn}>
+          <SomeComponent />
+        </ScaleTransition>
+      </Portal>
     </>
   );
 }

Tree

There were a lot of breaking changes for the Tree API and cannot be fully converted automatically for complex implementations. The list of changes are:

Typescript type changes:

The new renderer prop on the Tree should be a component that accepts the following props:

One of the main differences with this new API is that the expanded, selected, and focused states are no longer provided by default since it is handled by the TreeItem component internally. Otherwise, the TreeItem no longer supports the expanderLeft icon so icons must be provided on the Tree component itself using the TreeProvider around each TreeItem.

See the Custom Tree Item Renderer example for an in-depth example using the API or the diff shown below for convert-get-item-props

All the following codemods can be run at once using:

npx @react-md/codemod v5-to-v6/tree/everything
@@ -1,13 +1,21 @@
+// TODO: The `getItemProps` have been removed from the `Tree` component and need to be manually changed. The `renderer` prop is the closest in functionality.
 import cn from "classnames";
-import type { ReactElement, ReactNode } from "react";
-import type { GetItemProps, TreeData, TreeItemIds } from "react-md";
+import { ReactElement, ReactNode, useId } from "react";
+
 import {
   ArrowDropDownSVGIcon,
+  DefaultTreeItemRenderer,
   FolderOpenSVGIcon,
   FolderSVGIcon,
   Tree,
-  useTreeItemExpansion,
-  useTreeItemSelection,
+  TreeData,
+  TreeItemNode,
+  TreeItemRendererProps,
+  TreeProvider,
+  useKeyboardMovementContext,
+  useTreeContext,
+  useTreeExpansion,
+  useTreeSelection,
 } from "react-md";

 import Html5SVGIcon from "icons/Html5SVGIcon";
@@ -19,7 +27,7 @@ import createIdGenerator from "./createIdGenerator";
 import styles from "./CustomizingTreeItems.module.scss";

 type ItemType = "folder" | "html" | "typescript" | "scss" | "text";
-interface Item extends TreeItemIds {
+interface Item extends TreeItemNode {
   name: string;
   type: ItemType;
 }
@@ -28,7 +36,7 @@ const uuid = createIdGenerator("custom-tree-items");
 const createItem = (
   name: string,
   type: ItemType,
-  parentId: string | null = null
+  parentId: string | null = null,
 ): Item => {
   const itemId = uuid();
   return {
@@ -57,11 +65,25 @@ const data = [
   index,
 ].reduce<TreeData<Item>>(
   (tree, item) => ({ ...tree, [item.itemId]: item }),
-  {}
+  {},
 );

-const getItemProps: GetItemProps<Item> = (item) => {
-  const { selected, focused, expanded, type } = item;
+// TODO: This might need to be renamed to match normal component naming conventions
+const getItemProps = function Renderer(
+  props: TreeItemRendererProps<Item>,
+): ReactElement {
+  const { item } = props;
+
+  const { type, itemId } = item;
+
+  const context = useTreeContext();
+
+  const { expandedIds, selectedIds } = context;
+
+  const expanded = expandedIds.has(itemId);
+  const selected = selectedIds.has(itemId);
+  const id = useId();
+  const focused = useKeyboardMovementContext().activeDescendantId === id;
   let leftAddon: ReactNode = null;
   switch (type) {
     case "folder":
@@ -82,22 +104,24 @@ const getItemProps: GetItemProps<Item> = (item) => {
     // no default
   }

-  return {
-    leftAddon,
-    expanderIcon: <ArrowDropDownSVGIcon />,
-    className: cn(styles.item, {
-      [styles.focused]: focused,
-      [styles.selected]: selected,
-    }),
-  };
+  return (
+    <TreeProvider {...context} expanderIcon={<ArrowDropDownSVGIcon />}>
+      <DefaultTreeItemRenderer
+        {...props}
+        leftAddon={leftAddon}
+        className={cn(styles.item, {
+          [styles.focused]: focused,
+          [styles.selected]: selected,
+        })}
+        id={id}
+      />
+    </TreeProvider>
+  );
 };

 export default function Demo(): ReactElement {
-  const selection = useTreeItemSelection([demo.itemId], false);
-  const expansion = useTreeItemExpansion([
-    srcFolder.itemId,
-    publicFolder.itemId,
-  ]);
+  const selection = useTreeSelection([demo.itemId], false);
+  const expansion = useTreeExpansion([srcFolder.itemId, publicFolder.itemId]);

   return (
     <Tree
🔧 convert-get-item-props
npx @react-md/codemod v5-to-v6/tree/convert-get-item-props
 import cn from "classnames";
-import type { ReactNode } from "react";
-import type { GetItemProps } from "react-md";
+import { ReactElement, ReactNode, useId } from "react";
 import {
   ArrowDropDownSVGIcon,
+  DefaultTreeItemRenderer,
   FolderOpenSVGIcon,
   FolderSVGIcon,
+  TreeItemRendererProps,
+  TreeProvider,
+  useKeyboardMovementContext,
+  useTreeContext,
 } from "react-md";

 import Html5SVGIcon from "icons/Html5SVGIcon";
 import FileSVGIcon from "./FileSVGIcon";
 import SassSVGIcon from "./SassSVGIcon";
 import TypescriptSVGIcon from "./TypescriptSVGIcon";
 import { Item } from "./anotherFile";
 import styles from "./styles.module.scss";

-export const getItemProps: GetItemProps<Item> = ({
-  selected,
-  focused,
-  expanded,
-  type,
-  ...item
-}) => {
+// TODO: This might need to be renamed to match normal component naming conventions
+export const getItemProps = function Renderer(
+  props: TreeItemRendererProps<Item>,
+): ReactElement {
+  const { item: tempItem } = props;
+
+  const { type, itemId, ...item } = tempItem;
+
+  const context = useTreeContext();
+
+  const { expandedIds, selectedIds } = context;
+
+  const expanded = expandedIds.has(itemId);
+  const selected = selectedIds.has(itemId);
+  const id = useId();
+  const focused = useKeyboardMovementContext().activeDescendantId === id;
   let leftAddon: ReactNode = null;
   switch (type) {
     case "folder":
       leftAddon = expanded ? <FolderOpenSVGIcon /> : <FolderSVGIcon />;
       break;
     case "html":
       leftAddon = <Html5SVGIcon />;
       break;
     case "text":
       leftAddon = <FileSVGIcon />;
       break;
     case "scss":
       leftAddon = <SassSVGIcon />;
       break;
     case "typescript":
       leftAddon = <TypescriptSVGIcon />;
       break;
     // no default
   }

-  return {
-    leftAddon,
-    expanderIcon: <ArrowDropDownSVGIcon />,
-    className: cn(styles.item, {
-      [styles.focused]: focused,
-      [styles.selected]: selected,
-    }),
-  };
+  return (
+    <TreeProvider {...context} expanderIcon={<ArrowDropDownSVGIcon />}>
+      <DefaultTreeItemRenderer
+        {...props}
+        leftAddon={leftAddon}
+        className={cn(styles.item, {
+          [styles.focused]: focused,
+          [styles.selected]: selected,
+        })}
+        id={id}
+      />
+    </TreeProvider>
+  );
 };
🔧 item-renderer-to-render-component
npx @react-md/codemod v5-to-v6/tree/item-renderer-to-render-component
-import { TreeItemRenderer, defaultTreeItemRenderer } from "react-md";
+// TODO: The `defaultTreeItemRenderer` has been replaced by the `DefaultTreeItemRenderer` component and cannot automatically be converted
+// TODO: The `TreeItemRenderer` type has been replaced by the `TreeItemRendererProps` type and cannot automatically be converted
+import { TreeItemRendererProps, DefaultTreeItemRenderer } from "react-md";
 import { MyTreeItem } from "./types";
 import MyFancyNonTreeItem from "./MyFancyNonTreeItem";

 export const itemRenderer: TreeItemRenderer<MyTreeItem> = (
   itemProps,
   item,
   treeProps
 ) => {
   const { key } = itemProps;
   const { isCustom } = item;
   if (isCustom) {
     return <MyFancyNonTreeItem item={item} key={key} />;
   }

   return defaultTreeItemRenderer(itemProps, item, treeProps);
 };
✅ update-simple-types
npx @react-md/codemod v5-to-v6/tree/update-simple-types
-import { TreeItemId, TreeItemIds, ExpandedIds, SelectedIds } from "react-md";
+import { TreeItemIdSet, TreeItemNode } from "react-md";

-export interface Example1 extends TreeItemIds {
-  itemId: TreeItemId;
-  selectedIds: SelectedIds;
-  expandedIds: ExpandedIds;
+export interface Example1 extends TreeItemNode {
+  itemId: string;
+  selectedIds: TreeItemIdSet;
+  expandedIds: TreeItemIdSet;
 }
🔧 update-tree-props
npx @react-md/codemod v5-to-v6/tree/update-tree-props
   return (
     <>
       <Tree
-        labelKey="example"
-        valueKey="example-2"
-        getItemLabel={(item) => item.something}
-        getItemValue={(item) => item.somethingElse}
-        onItemExpansion={(itemId) => {
+        toggleTreeItemExpansion={(itemId) => {
           expand(itemId);
         }}
-        onMultiItemExpansion={(itemIds) => {
+        expandMultipleTreeItems={(itemIds) => {
           setExpanded(itemIds);
         }}
-        onItemSelect={(itemId) => {
+        toggleTreeItemSelection={(itemId) => {
           select(itemId);
         }}
-        onMultiItemSelect={(itemIds) => {
+        selectMultipleTreeItems={(itemIds) => {
           multiSelect(itemIds);
         }}
       />
🔧 use-tree-hooks
npx @react-md/codemod v5-to-v6/tree/use-tree-hooks
 import type { ReactElement } from "react";
-import { Tree, useTreeItemSelection, useTreeItemExpansion } from "react-md";
+import { Tree, useTreeSelection, useTreeExpansion } from "react-md";

 import folders from "./folders";

 export default function Demo(): ReactElement {
-  const { selectedIds, multiSelect, onItemSelect, onMultiItemSelect } =
-    useTreeItemSelection([], false);
-  const { expandedIds, onItemExpansion, onMultiItemExpansion } =
-    useTreeItemExpansion([]);
+  const {
+    selectedIds,
+    multiSelect,
+    toggleTreeItemSelection: onItemSelect,
+    selectMultipleTreeItems: onMultiItemSelect,
+  } = useTreeSelection([], false);
+  const {
+    expandedIds,
+    toggleTreeItemExpansion: onItemExpansion,
+    expandMultipleTreeItems: onMultiItemExpansion,
+  } = useTreeExpansion([]);

   return (
     <Tree

Tooltip

🔧 convert-use-tooltip
npx @react-md/codemod v5-to-v6/tooltip/convert-use-tooltip

The useTooltip went through an API change since it no longer uses the shared "hover mode API" and instead has its own TooltipHoverModeProvider. The other changes are:

+// TODO: `useTooltip` no longer supports the `disableHoverMode` option and was removed. See the new hover mode API docs or define the `hoverTimeout` to option.
 import { type ReactElement } from "react";
 import { Button, Tooltip, useTooltip } from "react-md";

 export default function Example(): ReactElement {
   const { elementProps, tooltipProps } = useTooltip({
-    baseId: "example-1-id",
+    id: "example-1-id",
+
     onClick(event) {
       // DO SOMETHING
     },
-    touchTime: 500,
-    focusTime: 400,
-    disableHoverMode: true,
+
+    hoverTimeout: 400,
   });
   return (
     <>

If the hover mode API was used, there will be a diff similar to:

+// TODO: `useTooltip` no longer returns the following keys from the hover mode api and must manually be updated: stuck, active, onClick, onMouseEnter, onMouseLeave
+// TODO: `useTooltip` no longer returns the `handlers` object. The event handlers can be extracted from the `elementProps` if they are still needed.
 import { type ReactElement } from "react";
-import { Button, Tooltip, useTooltip } from "react-md";
+import { Button, Tooltip, useTooltip, useTooltipHoverMode } from "react-md";

 export default function Example(): ReactElement {
   const {
-    elementProps,
-    tooltipProps,
-    stuck,
-    active,
-    onClick,
-    onMouseEnter,
-    onMouseLeave,
-    handlers,
     enableHoverMode: enable,
     disableHoverMode,
     startDisableTimer,
-    clearHoverTimeout,
+  } = useTooltipHoverMode();
+
+  const {
+    elementProps,
+    tooltipProps,
+    clearVisibilityTimeout: clearHoverTimeout,
   } = useTooltip({
-    baseId: "example-1-id",
+    id: "example-1-id",
     onClick(event) {
       // DO SOMETHING
     },
🔧 remove-tooltipped-component
npx @react-md/codemod v5-to-v6/tooltip/remove-tooltipped-component

The Tooltipped component has been removed and a custom implementation must be used instead.

+// TODO: The `Tooltipped` component has been removed. Update the code to use the `useTooltip` hook instead.
 import { type ReactNode } from "react";
-import { SimplePosition, Tooltipped } from "react-md";
+import { SimplePosition, useTooltip } from "react-md";

 interface Test1Props {
   onClick?: (event: MouseEvent) => void;
🔧 update-tooltip-props
npx @react-md/codemod v5-to-v6/tooltip/update-tooltip-props

The Tooltip component updated the following props:

   return (
     <>
       <Tooltip>Hello</Tooltip>
-      <Tooltip portal>Hello</Tooltip>
-      <Tooltip portal={false}>Hello</Tooltip>
-      <Tooltip portalInto={() => document.getElementById("some-node")}>
-        Hello
-      </Tooltip>
-      <Tooltip portalIntoId="some-dom-id">Hello</Tooltip>
-      <Tooltip lineWrap>Hello</Tooltip>
-      <Tooltip lineWrap={true}>Hello</Tooltip>
-      <Tooltip lineWrap={lineWrap}>Hello</Tooltip>
-      <Tooltip lineWrap={false}>Hello</Tooltip>
+      <Tooltip>Hello</Tooltip>
+      <Tooltip disablePortal>Hello</Tooltip>
+      <Tooltip>Hello</Tooltip>
+      <Tooltip>Hello</Tooltip>
+      <Tooltip>Hello</Tooltip>
+      <Tooltip>Hello</Tooltip>
+      <Tooltip textOverflow={lineWrap ? "allow" : "nowrap"}>Hello</Tooltip>
+      <Tooltip textOverflow="nowrap">Hello</Tooltip>
     </>
   );
 }

Typography

❌ remove-removed-types
npx @react-md/codemod v5-to-v6/typography/remove-removed-types

The following types were removed and cannot be migrated automatically.

-import {
-  TextContainerSize,
-  TextContainerRenderFunction,
-  TypographyRenderFunction,
-} from "react-md";
-
+// TODO: Remove the `TypographyRenderFunction` usage since it no longer exists.
+// TODO: Remove the `TextContainerRenderFunction` usage since it no longer exists.
+// TODO: Remove the `TextContainerSize` usage since it no longer exists.
 const x: TextContainerRenderFunction = ({ className }) => <>{className}</>;
 const y: TypographyRenderFunction = ({ className }) => <>{className}</>;
✅ update-text-container-props
npx @react-md/codemod v5-to-v6/typography/update-text-container-props

The TextContainer no longer supports the size prop and should be removed.

       <TextContainer>
         <Typography>Hello, world!</Typography>
       </TextContainer>
-      <TextContainer size="auto">
+      <TextContainer>
         <Typography>Hello, world!</Typography>
       </TextContainer>
-      <TextContainer size="mobile">
+      <TextContainer>
         <Typography>Hello, world!</Typography>
       </TextContainer>
-      <TextContainer size={size}>
+      <TextContainer>
         <Typography>Hello, world!</Typography>
       </TextContainer>
     </>
✅ update-typography-props
npx @react-md/codemod v5-to-v6/typography/update-typography-props

The Typography and SrOnly components renamed the component prop to as.

 export default function Example(): ReactElement {
   return (
     <>
-      <Typography component="div">Hello, world!</Typography>
-      <Typography component={CustomElement}>Hello, world!</Typography>
-      <SrOnly component="div">Hello, world!</SrOnly>
-      <SrOnly component={CustomElement}>Hello, world!</SrOnly>
+      <Typography as="div">Hello, world!</Typography>
+      <Typography as={CustomElement}>Hello, world!</Typography>
+      <SrOnly as="div">Hello, world!</SrOnly>
+      <SrOnly as={CustomElement}>Hello, world!</SrOnly>
     </>
   );
 }

In addition, the Typography and SrOnly components no longer support the child render function behavior and prefer the typography() or srOnly() class name utility functions instead.

❌ This cannot be updated automatically.

+// TODO: Check the `Typography` usage to see if using the removed children function renderer behavior for getting the class name.
 import { type ReactElement } from "react";
-import { Typography } from "react-md";
+import { Typography } from "react-md";
 import CustomComponent from "./CustomComponent";

 export default function Example(): ReactElement {
   return (
-    <Typography
-      children={({ className }) => (
-        <CustomComponent classNameProp={className}>
-          Hello, world!
-        </CustomComponent>
-      )}
-    />
+    <CustomComponent classNameProp={typography()}>
+      Hello, world!
+    </CustomComponent>
   );
 }

Utils

✅ dir-to-writing-direction-provider
npx @react-md/codemod v5-to-v6/utils/dir-to-writing-direction-provider

There have been a few breaking changes around handling the dir attribute:

Typescript only:

 import { type ReactElement } from "react";
-import { Dir, DirProps, DEFAULT_DIR, WritingDirection, useDir } from "react-md";
+import {
+  WritingDirectionProvider,
+  WritingDirectionProviderProps,
+  DEFAULT_WRITING_DIRECTION,
+  Dir,
+  useDir,
+} from "react-md";

-export function Example1(props: DirProps): ReactElement {
-  return <Dir defaultDir={DEFAULT_DIR} {...props} />;
+export function Example1(props: WritingDirectionProviderProps): ReactElement {
+  return (
+    <WritingDirectionProvider
+      defaultDir={DEFAULT_WRITING_DIRECTION}
+      {...props}
+    />
+  );
 }

 export function Example2(): ReactElement {
-  let dir: WritingDirection = "ltr";
+  let dir: Dir = "ltr";
   if (something) {
     dir = "rtl";
   }

-  return <Dir defaultDir={dir} />;
+  return <WritingDirectionProvider defaultDir={dir} />;
 }

 export function Example3(): ReactElement {
✅ nearest-parameters-to-object
npx @react-md/codemod v5-to-v6/utils/nearest-parameters-to-object

The nearest utility function was updated to accept a single object parameter instead of multiple parameters.

 const steps = range;
 const minValue = 2000;
 const maxValue = 8000;
-expect(nearest(10000, min, maxValue, steps, range)).toBe(maxValue);
-expect(nearest(0, minValue, max, steps, range)).toBe(minValue);
+expect(
+  nearest({
+    value: 10000,
+    min: min,
+    max: maxValue,
+    steps: steps,
+    range: range,
+  }),
+).toBe(maxValue);
+expect(
+  nearest({
+    value: 0,
+    min: minValue,
+    max: max,
+    steps: steps,
+    range: range,
+  }),
+).toBe(minValue);
❌ remove-scroll-listener
npx @react-md/codemod v5-to-v6/utils/remove-scroll-listener

The useScrollListener hook and ScrollListener component have been removed and must be implemented manually.

+// TODO: The `ScrollListener`/`useScrollListener` have been removed from react-md. You will need to implement their behavior manually.
 import { ArrowDropDownSVGIcon } from "@react-md/material-icons";
 import type { CSSProperties, ReactElement } from "react";
 import { useCallback, useRef, useState } from "react";
@@ -6,12 +7,10 @@ import {
   List,
   ListItem,
   Overlay,
-  ScrollListener,
   TextIconSpacing,
   Typography,
   getFixedPosition,
   useToggle,
-  useScrollListener,
 } from "react-md";

 import styles from "./SimpleExample.module.scss";
@@ -38,10 +37,6 @@ export default function SimpleExample(): ReactElement {
     // eslint-disable-next-line react-hooks/exhaustive-deps
   }, []);

-  useScrollListener({
-    onUpdate: updatePosition,
-  });
-
   return (
     <div className={styles.container}>
       <Button
@@ -77,7 +72,6 @@ export default function SimpleExample(): ReactElement {
             }
           }}
         >
-          <ScrollListener onScroll={updatePosition} />
           {Array.from({ length: 6 }).map((_, i) => (
             <ListItem id={`menu-item-${i}`} key={i} role="menuitem">
               {`Option ${i + 1}`}

✅ update-resize-listener
npx @react-md/codemod v5-to-v6/utils/update-resize-listener

The useResizeListener options have been updated as follows:

 export default function Example(): null {
   const [size, setSize] = useState(window.innerWidth);
   useResizeListener({
-    onResize() {
+    onUpdate() {
       setSize(window.innerWidth);
     },
   });

   useResizeListener({
-    onResize: () => setSize(window.innerWidth),
-    enabled: false,
+    onUpdate: () => setSize(window.innerWidth),
+    disabled: true,
   });
   useResizeListener({
-    onResize: () => setSize(window.innerWidth),
-    enabled: someFlag || anotherFlag,
+    onUpdate: () => setSize(window.innerWidth),
+    disabled: !(someFlag || anotherFlag),
   });
   useResizeListener({
-    immediate: false,
-    onResize: () => setSize(window.innerWidth),
-    options: false,
+    onUpdate: () => setSize(window.innerWidth),
+    capture: false,
   });
   useResizeListener({
-    immediate: someFlag,
-    onResize: () => {
+    onUpdate: () => {
       console.log(window.innerWidth);
     },
-    options: {
-      once: true,
-      passive: true,
-    },
+    once: true,
+    passive: true,
   });

In addition, the ResizeListener component has been removed in favor of using the useResizeListener hook.

 import type { ReactElement } from "react";
 import { useState } from "react";
-import { Checkbox, ResizeListener, Typography, useChecked } from "react-md";
+import { Checkbox, Typography, useChecked, useResizeListener } from "react-md";

 import CodeBlock from "./CodeBlock";

@@ -16,6 +16,11 @@ export default function Demo(): ReactElement {
   const [enabled, handleEnabledChange] = useChecked(true);
   const [immediate, handleImmediateChange] = useChecked(true);

+  useResizeListener({
+    onUpdate: () => setSize(window.innerWidth),
+    disabled: !enabled,
+  });
+
   return (
     <>
       <Checkbox
@@ -32,12 +37,7 @@ export default function Demo(): ReactElement {
         onChange={handleImmediateChange}
         label="Invoke on mount"
       />
-      {enabled && (
-        <ResizeListener
-          immediate={immediate}
-          onResize={() => setSize(window.innerWidth)}
-        />
-      )}
+
       <Typography>The current app size is:</Typography>
       <CodeBlock suppressHydrationWarning>{size}px</CodeBlock>
     </>
✅ update-use-resize-observer
npx @react-md/codemod v5-to-v6/utils/update-use-resize-observer

The useResizeObserver hook was updated so that it:

 export default function Example(): ReactElement {
   const [state, setState] = useState();
-  const [, refCallback] = useResizeObserver(
-    useCallback(
-      (resizeData) =>
-        setState({
-          height: resizeData.scrollHeight,
-          width: resizeData.scrollWidth,
-        }),
-      []
-    )
-  );
+  const refCallback = useResizeObserver({
+    onUpdate: useCallback((entry) => {
+      setState({
+        height: entry.target.scrollHeight,
+        width: entry.target.scrollWidth,
+      });
+    }, []),
+  });

   return <></>;
 }

If a ref is required within the component, provide it using the ref option like before:

 export default function Example(): ReactElement {
   const [state, setState] = useState();
   const nodeRef = useRef();
-  const [ref, refCallback] = useResizeObserver(
-    useCallback(
-      (resizeData) =>
-        setState({
-          height: resizeData.scrollHeight,
-          width: resizeData.scrollWidth,
-        }),
-      []
-    ),
-    {
-      ref: nodeRef,
-    }
-  );
+  const refCallback = useResizeObserver({
+    onUpdate: useCallback((entry) => {
+      setState({
+        height: entry.target.scrollHeight,
+        width: entry.target.scrollWidth,
+      });
+    }, []),
+
+    ref: nodeRef,
+  });

  // do stuff with `nodeRef.current`

If the nodeRef is dynamic, wrap with useEnsuredRef:

 @@ -5,7 +5,7 @@ import {
   useEffect,
   useState,
 } from "react";
-import { useResizeObserver } from "react-md";
+import { useEnsuredRef, useResizeObserver } from "react-md";

 interface ExampleProps {
   nodeRef?: Ref<HTMLDivElement>;
@@ -13,19 +13,17 @@ interface ExampleProps {

 export default function Example({ nodeRef }: ExampleProps): ReactElement {
   const [state, setState] = useState();
-  const [ref, refCallback] = useResizeObserver(
-    useCallback(
-      (resizeData) =>
-        setState({
-          height: resizeData.scrollHeight,
-          width: resizeData.scrollWidth,
-        }),
-      []
-    ),
-    {
-      ref: nodeRef,
-    }
-  );
+  const [ref, nodeRefCallback] = useEnsuredRef(nodeRef);
+  const refCallback = useResizeObserver({
+    onUpdate: useCallback((entry) => {
+      setState({
+        height: entry.target.scrollHeight,
+        width: entry.target.scrollWidth,
+      });
+    }, []),
+
+    ref: nodeRefCallback,
+  });

   useEffect(() => {
     if (ref.current) {
✅ update-use-toggle-api
npx @react-md/codemod v5-to-v6/utils/update-use-toggle-api

The useToggle hook was updated to return an object instead of an ordered array.

 import { type ReactElement } from "react";
 import { Button, useToggle } from "react-md";

 export default function Example(): ReactElement {
-  const [toggled, enable, disable, toggle] = useToggle();
+  const {
+    toggled: toggled,
+    enable: enable,
+    disable: disable,
+    toggle: toggle,
+  } = useToggle();
   return (
     <>
       <Button onClick={enable}>Enable</Button>
       <Button onClick={disable}>Diable</Button>
       <Button onClick={toggle}>Toggle</Button>
       {toggled && <>Hello, world!</>}
     </>
   );
 }
✅ within-range-parameters-to-object
npx @react-md/codemod v5-to-v6/utils/within-range-parameters-to-object

The withinRange util was updated to accept a single object parameter instead of multiple parameters.

-expect(withinRange(0, 0, 10)).toBe(0);
-expect(withinRange(-1, 0, 10)).toBe(0);
+expect(
+  withinRange({
+    min: 0,
+    max: 10,
+    value: 0,
+  }),
+).toBe(0);
+expect(
+  withinRange({
+    min: 0,
+    max: 10,
+    value: -1,
+  }),
+).toBe(0);
🔧 update-search-functions
npx @react-md/codemod v5-to-v6/utils/update-search-functions

There were a few breaking changes for the caseInsensitiveFilter, fuzzyFilter, and findIgnoreCase functions:

+// TODO: Check if `caseInsensitiveSearch` is using a list of objects and requires an `extractor` option
+// TODO: Check if `fuzzySearch` is using a list of objects and requires an `extractor` option
+import { caseInsensitiveSearch, fuzzySearch } from "react-md";
-import { caseInsensitiveFilter, findIgnoreCase, fuzzyFilter } from "react-md";

 const FRUITS = ["Apple", "Banana", "Mango", "Orange"];

-expect(caseInsensitiveFilter("", list1)).toBe(list1);
-expect(caseInsensitiveFilter("ap", FRUITS, { startsWith: true })).toEqual([
-  "Apple",
-]);
-expect(caseInsensitiveFilter("an", FRUITS, { startsWith: true })).toEqual([]);
+expect(caseInsensitiveSearch({ query: "", list: list1 })).toBe(list1);
+expect(
+  caseInsensitiveSearch({
+    query: "ap",
+    list: FRUITS,
+    whitespace: "trim",
+    startsWith: true,
+  })
+).toEqual(["Apple"]);
+expect(
+  caseInsensitiveSearch({ query: "an", list: FRUITS, whitespace: "ignore" })
+).toEqual([]);

 const list = ["Item 1", "This is Item 1"];
-expect(caseInsensitiveFilter("item", list, { startsWith: true })).toEqual([
-  "Item 1",
-]);
+expect(
+  caseInsensitiveSearch({
+    query: "item",
+    list,
+  })
+).toEqual(["Item 1"]);

 const item1 = "Lorem ipsum";
 const item2 = "another item";
 const item3 = "in this string";
 const item4 = "not interested, mate";
 const item5 = "not in my house";
 const list2 = [item1, item2, item3, item4, item5];

-expect(fuzzyFilter("ti", list2)).toEqual([item3]);
-expect(fuzzyFilter("ti", list2, { ignoreWhitespace: true })).toEqual([
-  item2,
-  item3,
-  item4,
-  item5,
-]);
-expect(fuzzyFilter("t i", list2, { ignoreWhitespace: true })).toEqual([
-  item2,
-  item3,
-  item4,
-  item5,
-]);
-expect(fuzzyFilter("rem", list2, { ignoreWhitespace: true })).toEqual([
-  item1,
-  item2,
-]);
-expect(fuzzyFilter("tem", list2, { ignoreWhitespace: true })).toEqual([item2]);
+expect(fuzzySearch({ query: "ti", list: list2 })).toEqual([item3]);
+expect(fuzzySearch({ query: "ti", list: list2, whitespace: "ignore" })).toEqual(
+  [item2, item3, item4, item5]
+);
+expect(
+  fuzzySearch({ query: "t i", list: list2, whitespace: "ignore" })
+).toEqual([item2, item3, item4, item5]);
+expect(
+  fuzzySearch({ query: "rem", list: list2, whitespace: "ignore" })
+).toEqual([item1, item2]);
+expect(
+  fuzzySearch({ query: "tem", list: list2, whitespace: "ignore" })
+).toEqual([item2]);

-expect(findIgnoreCase("", list)).toBe(null);
-expect(findIgnoreCase("f", list)).toBe(item1);
-expect(findIgnoreCase("s", list)).toBe(item2);
-expect(findIgnoreCase("thi", list)).toBe(item3);
-const getItemValue1 = (item: string) => `thing-${item}`;
-expect(findIgnoreCase("thing-a", FRUITS, { getItemValue: getItemValue1 })).toBe(
-  "Apple"
+expect(caseInsensitiveSearch({ query: "", list, type: "search" })).toBe(null);
+expect(caseInsensitiveSearch({ query: "f", list, type: "search" })).toBe(item1);
+expect(caseInsensitiveSearch({ query: "s", list, type: "search" })).toBe(item2);
+expect(caseInsensitiveSearch({ query: "thi", list, type: "search" })).toBe(
+  item3
 );
+const getItemValue1 = (item: string) => `thing-${item}`;
+expect(
+  caseInsensitiveSearch({
+    query: "thing-a",
+    list: FRUITS,
+    type: "search",
+    extractor: getItemValue1,
+  })
+).toBe("Apple");

Sass/SCSS Migration

npx @react-md/codemod sass-migrator v5-to-v6

The provided codemod will attempt to handle most of the sass migrations except for the following:

The codemod will:

High Level Changes

This version of react-md only supports the new Sass module system through @use where the main changes are:

Color Scheme Changes

This version of react-md now supports switching between, light, dark, and system color schemes by setting a new $color-scheme variable instead of the $rmd-theme-light and $rmd-theme-dark-class variables. If you were not using the prefers-color-scheme behavior for the $rmd-theme-dark-class, just change $rmd-theme-light: true|false to $color-scheme: light|dark

 @use "react-md" with (
+  $color-scheme: dark
-  $rmd-theme-light: false
 );

If you were using the $rmd-theme-dark-class: prefers-color-scheme, set $color-scheme: system and $disable-default-system-theme: true:

 @use "react-md" with (
+  $color-scheme: system,
+  $disable-default-system-theme: true
-  $rmd-theme-light: false,
-  $rmd-theme-dark-class: 'prefers-color-scheme'
 );

If you want to use the new system theme behavior, remove the $disable-default-system-theme and every @media (prefers-color-scheme: dark) that exist in your app.

Theme 🔧

-@include rmd-theme(background-color, background);
+@include theme-use-var(background-color, background-color);

-@include rmd-theme(background-color, surface);
+@include theme-use-var(background-color, surface-color);

 /* there is no `on-surface` variable type in v6, but inverse-color or text-primary-color are the closest */
-@include rmd-theme(color, on-surface);
+@include theme-use-var(color, inverse-color);

-@include rmd-theme(background-color, primary);
-@include rmd-theme(color, on-primary);
+@include theme-use-var(background-color, primary-color);
+@include theme-use-var(color, on-primary-color);

-@include rmd-theme(background-color, secondary);
-@include rmd-theme(color, on-secondary);
+@include theme-use-var(background-color, secondary-color);
+@include theme-use-var(color, on-secondary-color);

-@include rmd-theme(background-color, warning);
-@include rmd-theme(color, on-warning);
+@include theme-use-var(background-color, warning-color);
+@include theme-use-var(color, on-warning-color);

-@include rmd-theme(background-color, error);
-@include rmd-theme(color, on-error);
+@include theme-use-var(background-color, error-color);
+@include theme-use-var(color, on-error-color);

-@include rmd-theme(background-color, success);
-@include rmd-theme(color, on-success);
+@include theme-use-var(background-color, success-color);
+@include theme-use-var(color, on-success-color);

-@include rmd-theme(color, text-primary-on-background);
+@include theme-use-var(color, text-primary-color);

-@include rmd-theme(color, text-secondary-on-background);
+@include theme-use-var(color, text-secondary-color);

-@include rmd-theme(color, text-hint-on-background);
+@include theme-use-var(color, text-hint-color);

-@include rmd-theme(color, text-disabled-on-background);
+@include theme-use-var(color, text-disabled-color);

-@include rmd-theme(color, text-icon-on-background);
+@include icon-use-var(color);
-@include rmd-theme(background-color, light-background);
-@include rmd-theme(background-color, light-surface);
-@include rmd-theme(background-color, dark-background);
-@include rmd-theme(background-color, dark-surface);
-@include rmd-theme(color, text-primary-on-light);
-@include rmd-theme(color, text-secondary-on-light);
-@include rmd-theme(color, text-hint-on-light);
-@include rmd-theme(color, text-disabled-on-light);
-@include rmd-theme(color, text-icon-on-light);
-@include rmd-theme(color, text-primary-on-dark);
-@include rmd-theme(color, text-secondary-on-dark);
-@include rmd-theme(color, text-hint-on-dark);
-@include rmd-theme(color, text-disabled-on-dark);
-@include rmd-theme(color, text-icon-on-dark);

Alert 🔧

App Bar 🔧

Autocomplete 🎉

The previous version did not have any custom Sass/SCSS.

Avatar 🔧

Badge 🔧

Button 🔧

Card 🔧

Chip 🔧

Dialog 🔧

Divider 🔧

Elevation 🔧

Expansion Panel 🔧

Form 🔧

Icon 🔧

Layout 🔧

List 🔧

Media 🔧

Overlay 🔧

Progress 🔧

Sheet 🔧

States 🔧

Table 🔧

Tabs 🔧

Tooltip 🔧

Transition 🔧

Tree 🔧

Typography 🔧

Utils 🔧