Skip to content

Component Library migration guide

Before proceeding with the migration, make sure to read the migration overview.

Once you’re ready to migrate, install Cimpress UI in your project by following the installation instructions:

Afterwards, consult this page for guidance on migrating specific components.

  • The default font size has increased from 14px to 16px. This change may require adjustments to your project’s layout.

  • Components no longer have built-in outer spacing. Spacing between components should be defined either by the parent component, or through the use of style props.

    // Before
    <TextField />
    <TextField />
    // After (spacing defined by parent component)
    <Stack gap={16}>
    <TextField />
    <TextField />
    </Stack>
    // After (spacing defined using a style prop)
    <TextField marginBottom={16} />
    <TextField />
  • className and style props have been renamed to UNSAFE_className and UNSAFE_style respectively. We strongly recommend removing any style overrides. See our styling guide for details.

In most cases, the Accordion component should be migrated to the Disclosure component.

  • The following props have been renamed:

    • customOpen becomes isExpanded
    • defaultOpen becomes defaultExpanded
  • Values for the variant prop are different:

    • 'default' and 'ghost' should be replaced with 'base'
    • 'minimal' should be replaced with 'subtle'
  • headerStyle and bodyStyle props are no longer supported, as Cimpress UI doesn’t allow style overrides.

  • onClose and onHeaderClick event handlers are no longer supported. Use onExpandedChange to detect when the disclosure expands/collapses.

  • title prop now only accepts string content, and nesting other components is no longer allowed. A restricted set of additional content can be added to the disclosure header using the iconStart, iconEnd, badge, and actions props.

Disclosures that form a logical unit should be grouped using the DisclosureGroup component. See disclosure usage guidelines for more information.

In most cases, the Alert component should be migrated to one of the following components:

  • Alert (if rendered dynamically in response to user action)
  • Callout (for all other use cases)
  • status prop has been renamed to tone. Most values are the same, with the following exception:

    • 'danger' becomes 'critical'
  • title prop now only accepts string content, and nesting other components is no longer allowed.

  • There is no message prop on Alert nor Callout. Instead, provide the contents as children of the component. Note that nesting other components within Alert/Callout is not allowed.

    // Before
    <Alert message="My message" />
    // After
    <Callout>My message</Callout>

    In the case of Callout, you may provide an actions prop with the buttons that will be displayed at the bottom of the callout.

    // Before
    <Alert
    message={
    <>
    <span>This is a callout with a button.</span>
    <Button>Learn more</Button>
    </>
    }
    />
    // After
    <Callout actions={<Button>Learn more</Button>}>This is a callout with a button.</Callout>
  • dismissible prop has been renamed to isDismissible.

  • dismissed prop is no longer supported. Use conditional rendering if you want to dismiss the alert/callout programmatically.

    // Before
    const [isDismissed, setIsDismissed] = useState(false);
    return <Alert dismissed={isDismissed} onDismiss={() => setIsDismissed(true)} message="..." />;
    // After
    const [isDismissed, setIsDismissed] = useState(false);
    return isDismissed ? <Callout onDismiss={() => setIsDismissed(true)}>...</Callout> : null;

In most cases, the Breadcrumbs component should be migrated to the Breadcrumbs component. Similarly, BreadcrumbItem should be migrated to BreadcrumbItem.

  • In Component Library, BreadcrumbItem was a presentational container that required providing an interactive element (e.g., a button) as its child. In Cimpress UI, BreadcrumbItem is itself interactive, and only accepts string content as children.

    // Before
    <BreadcrumbItem>
    <Button href="/">Home</Button>
    </BreadcrumbItem>
    // After
    <BreadcrumbItem href="/">Home</BreadcrumbItem>
  • active prop on BreadcrumbItem is no longer supported. The last BreadcrumbItem within Breadcrumbs is now assumed to refer to the current page, and it becomes non-interactive.

In most cases, the Button component should be migrated to one of the following components:

  • Some values for the variant prop are different:

    • 'default' becomes 'secondary'
    • 'link' becomes 'tertiary'
    • 'anchor' is no longer supported - choose a different variant or convert the button to a link
  • color prop has been renamed to tone. Values are renamed as follows:

    • 'primary' becomes 'base'
    • 'danger' becomes 'critical'
  • Values for the size prop have been renamed:

    • 'sm' becomes 'small'
    • 'default' becomes 'medium'
    • 'lg' becomes 'large'
  • The following props have been renamed:

    • disabled becomes isDisabled
    • blockLevel becomes fullWidth
    • onClick becomes onPress
    • onMouseEnter becomes onHoverStart
    • onMouseLeave becomes onHoverEnd
  • children prop now only accepts string content, and nesting other components is no longer allowed. If using Button or LinkButton, you can add additional icons to the button using the iconStart and iconEnd props.

    // Before
    <Button>
    <IconAdd />
    <span>Add</span>
    </Button>
    // After
    <Button iconStart={<IconAdd />}>
    Add
    </Button>
  • Objects provided as arguments to onPress, onHoverStart, and onHoverEnd have a different structure. If you relied on the previous structure, get in touch with us and let us know your use case.

Buttons were previously used as toggles by changing between the primary and secondary variants depending on whether the button was in the “on” or “off” state. This is now disallowed, and such buttons should be replaced with the ToggleButton component.

// Before
<Button variant={isBold ? 'primary' : 'secondary'}>Bold</Button>
// After
<ToggleButton isSelected={isBold}>Bold</ToggleButton>

Bear in mind that for this use case the button label MUST remain the same in both the “on” and “off” states.

Multiple related toggle buttons should be grouped using the ToggleButtonGroup component. See toggle button usage guidelines for details.

In most cases, the Checkbox component should be migrated to the Checkbox component.

  • The following props have been renamed:

    • checked becomes isSelected
    • indeterminate becomes isIndeterminate
    • disabled becomes isDisabled
  • In Component Library, a checkbox with both checked and indeterminate props would render as checked. In Cimpress UI, a checkbox with both isSelected and isIndeterminate will now render as indeterminate.

  • There is no label prop on Checkbox. Instead, provide the checkbox label as children of the Checkbox component. Note that nesting other components within Checkbox is not allowed.

    // Before
    <Checkbox label="I agree to the terms and conditions" />
    // After
    <Checkbox>I agree to the terms and conditions</Checkbox>

    If the checkbox is rendered without a visible label, a hidden label must still be provided to identify the checkbox’s purpose to assistive technologies. This can be done using the aria-label or aria-labelledby props.

  • onChange event handler now accepts a single boolean argument specifying whether the checkbox is selected.

    // Before
    <Checkbox
    label="..."
    onChange={(e) => console.log(`Checkbox is ${e.target.checked ? 'selected' : 'not selected'}`)}
    />
    // After
    <Checkbox
    onChange={(isSelected) => console.log(`Checkbox is ${isSelected : 'selected' : 'not selected'}`)}
    >
    ...
    </Checkbox>
  • whiteBackground prop is no longer supported. The checkbox icon will always render with a white background.

  • inline prop is no longer supported. Layout should be dictated by the parent element. If using CheckboxGroup, you can pass direction="horizontal" to stack checkboxes horizontally instead of vertically.

  • payload prop is no longer supported.

Related checkboxes should be grouped using the CheckboxGroup component. See checkbox usage guidelines for more information.

In most cases, the Copy component should be migrated to one of the following components:

  • variant prop has been repurposed, and no longer allows to switch between the “button” and “inline” versions of the Copy component. Instead, migrate to an appropriate component from the list above.

    If migrating to CopyButton, the variant prop can be used to switch between a secondary and tertiary button variants.

    If migrating to CopyInline, the variant prop can be used to pick a typography variant that the component is rendered with.

  • hoverText prop has been renamed to description on the CopyInline component. It is not supported on the CopyButton component.

  • onClick event handler has been renamed to onCopy. It doesn’t accept any arguments.

  • successMessage prop is no longer supported. Success and failure messages are exposed in the localization system as copyToClipboard.success and copyToClipboard.failure respectively.

  • duration prop is no longer supported. The message duration is not customizable.

  • If migrating to CopyButton: a textual label is required to identify the button’s purpose to assistive technologies. This can be a visible label (using the children prop) or a hidden one (using the aria-label or aria-labelledby props).

  • If migrating to CopyInline: the value prop is no longer supported. The copied text will be the same as the displayed text provided using the children prop.

In most cases, the Drawer component should be migrated to the Drawer component.

  • In Component Library, a button that opens a drawer had to be manually wired with the drawer. In Cimpress UI, the trigger button can be automatically associated with a drawer. To do this, wrap both the trigger button and the drawer itself in the <DrawerRoot> component. Make sure that the trigger button is provided first.

    // Before
    const [isDrawerOpen, setIsDrawerOpen] = useState(false);
    return (
    <>
    <Button onClick={() => setIsDrawerOpen(true)}>Open drawer</Button>
    <Drawer show={isDrawerOpen}>{/* Drawer content */}</Drawer>
    </>
    );
    // After
    return (
    <DrawerRoot>
    <Button>Open drawer</Button>
    <Drawer>{/* Drawer content */}</Drawer>
    </DrawerRoot>
    );
  • Drawer component only allows two child components: DrawerBody and DrawerActions. DrawerBody should wrap the main content of the drawer, and DrawerActions should wrap the actions that the user can take within the drawer. These actions will be rendered at the bottom of the drawer.

    This also means that the footer prop is no longer supported, as the footer is reserved for actions rendered by DrawerActions.

    // Before
    <Drawer footer={<Button>Action</Button>}>
    Drawer content
    </Drawer>
    // After
    <Drawer>
    <DrawerBody>
    Drawer content
    </DrawerBody>
    <DrawerActions>
    <Button>Action</Button>
    </DrawerActions>
    </Drawer>
  • The following props have been renamed:

    • show becomes isOpen
    • closeOnOutsideClick becomes isDismissible
  • header prop has been renamed to title, and it now only accepts string content. Nesting other components is not allowed.

  • size prop now accepts one of three values: 'small', 'medium', or 'large'.

  • wrapperStyle, headerStyle, bodyStyle, and footerStyle props are no longer supported, as Cimpress UI doesn’t allow style overrides.

  • onRequestHide event handler is no longer supported. Use onOpenChange to detect when the drawer closes.

  • autoResize prop is no longer supported, as drawers have fixed sizes now.

  • position and zIndex props are no longer supported.

  • If the drawer is not dismissible, you must provide a way for the user to close the drawer. This can be done by passing a function as children to the Drawer component, and using the close function provided as an argument to that function.

    // Before
    const [isDrawerOpen, setIsDrawerOpen] = useState(true);
    return (
    <Drawer closeOnOutsideClick={false} footer={<Button onClick={() => setIsDrawerOpen(false)}>Close</Button>}>
    Drawer content
    </Drawer>
    );
    // After (uncontrolled)
    return (
    <Drawer isDismissible={false}>
    {({ close }) => (
    <>
    <DrawerBody>Drawer content</DrawerBody>
    <DrawerActions>
    <Button onPress={close}>Close</Button>
    </DrawerActions>
    </>
    )}
    </Drawer>
    );
    // After (controlled)
    const [isDrawerOpen, setIsDrawerOpen] = useState(true);
    return (
    <Drawer isOpen={isDrawerOpen} isDismissible={false}>
    <DrawerBody>Drawer content</DrawerBody>
    <DrawerActions>
    <Button onPress={() => setIsDrawerOpen(false)}>Close</Button>
    </DrawerActions>
    </Drawer>
    );

In most cases, the Dropdown component should be migrated to the Menu component.

  • The menu trigger button needs to be explicitly provided now. To do this, wrap both the trigger button and the menu itself in the <MenuRoot> component. Make sure that the trigger button is provided first.

    This also means that the title prop is no longer supported, and you should provide the label directly on the trigger button.

    // Before
    <Dropdown title="Menu">
    {/* Dropdown content */}
    </Dropdown>
    // After
    <MenuRoot>
    <Button iconEnd={<IconChevronDown />}>Menu</Button>
    <Menu>
    {/* Menu content */}
    </Menu>
    </MenuRoot>
  • In Component Library, Dropdown allowed all kinds of child elements within the dropdown list. In Cimpress UI, only three components are allowed as children of Menu:

    • MenuItem - renders an interactive menu item
    • MenuSection - groups menu items into sections
    • Divider - renders a divider between menu items/sections

    The MenuItem component is itself interactive, and doesn’t allow nesting other components within it. A restricted set of additional content can be added to the menu item using the description and icon props.

    // Before
    <Dropdown title="Menu">
    <button>Option 1</button>
    <button>Option 2</button>
    <hr />
    <a href="/">Go to homepage</a>
    </Dropdown>
    // After
    <MenuRoot>
    <Button iconEnd={<IconChevronDown />}>Menu</Button>
    <Menu>
    <MenuItem>Option 1</MenuItem>
    <MenuItem>Option 2</MenuItem>
    <Divider />
    <MenuItem href="/">Go to homepage</MenuItem>
    </Menu>
    </MenuRoot>

    To detect when a menu item has been pressed, use the onAction prop on either MenuItem or Menu:

    // Before
    <Dropdown title="Menu">
    <button onClick={() => console.log('Option 1 pressed')}>Option 1</button>
    <button onClick={() => console.log('Option 2 pressed')}>Option 2</button>
    </Dropdown>
    // After (with `onAction` on each menu item)
    <MenuRoot>
    <Button>...</Button>
    <Menu>
    <MenuItem onAction={() => console.log('Option 1 pressed')}>Option 1</MenuItem>
    <MenuItem onAction={() => console.log('Option 2 pressed')}>Option 2</MenuItem>
    </Menu>
    </MenuRoot>
    // After (with `onAction` on Menu)
    <MenuRoot>
    <Button>...</Button>
    <Menu onAction={(id) => console.log(`${id} pressed`)}>
    <MenuItem id="option-1">Option 1</MenuItem>
    <MenuItem id="option-2">Option 2</MenuItem>
    </Menu>
    </MenuRoot>

    Note that when using onAction on the Menu component, all menu items must have an id prop.

  • disabled prop is no longer supported. Use the isDisabled prop on the trigger button instead.

  • variant prop is no longer supported. You can customize the menu trigger button through its own props.

  • as prop is no longer supported.

The GlobalStyles component was used to inject global CSS-in-JS styles into the application. Cimpress UI uses a different approach for CSS, so the GlobalStyles component is no longer needed.

If you’re fully migrating to Cimpress UI, you can safely remove the GlobalStyles component from your component tree. If you’re using Component Library together with Cimpress UI, you should continue to render GlobalStyles as before - it will not impact Cimpress UI components.

In most cases, the Label component should be migrated to the Badge component.

  • status prop has been renamed to tone. Most values are the same, with the following exceptions:

    • 'primary' and 'default' become 'base'
    • 'danger' becomes 'critical'
  • Values for the size prop have been renamed:

    • 'sm' becomes 'small'
    • 'default' becomes 'medium'
    • 'lg' becomes 'large'
  • There is no text prop on Badge. Instead, provide the badge contents as children of the Badge component. Note that nesting other components within Badge is not allowed.

    // Before
    <Label text="Info" />
    // After
    <Badge>Info</Badge>

In most cases, the Modal component should be migrated to the ModalDialog component.

  • In Component Library, a button that opens a modal had to be manually wired with the modal. In Cimpress UI, the trigger button can be automatically associated with a modal dialog. To do this, wrap both the trigger button and the modal dialog itself in the <DialogRoot> component. Make sure that the trigger button is provided first.

    // Before
    const [isModalOpen, setIsModalOpen] = useState(false);
    return (
    <>
    <Button onClick={() => setIsModalOpen(true)}>Open modal</Button>
    <Modal show={isModalOpen}>{/* Modal content */}</Modal>
    </>
    );
    // After
    return (
    <DialogRoot>
    <Button>Open modal dialog</Button>
    <ModalDialog>{/* Modal dialog content */}</ModalDialog>
    </DialogRoot>
    );
  • ModalDialog component only allows two child components: ModalDialogBody and ModalDialogActions. ModalDialogBody should wrap the main content of the modal, and ModalDialogActions should wrap the actions that the user can take within the modal. These actions will be rendered at the bottom of the modal dialog.

    This also means that the footer prop is no longer supported, as the footer is reserved for actions rendered by ModalDialogActions.

    // Before
    <Modal footer={<Button>Action</Button>}>
    Modal content
    </Modal>
    // After
    <ModalDialog>
    <ModalDialogBody>
    Modal dialog content
    </ModalDialogBody>
    <ModalDialogActions>
    <Button>Action</Button>
    </ModalDialogActions>
    </ModalDialog>
  • The following props have been renamed:

    • show becomes isOpen
    • closeOnOutsideClick becomes isDismissible
  • Values for the size prop have been renamed:

    • 'md' becomes 'small'
    • 'lg' becomes 'medium'
    • 'xl' becomes 'large'
  • title prop now only accepts string content, and nesting other components is not allowed.

  • wrapperStyle prop is no longer supported, as Cimpress UI doesn’t allow style overrides.

  • onRequestHide event handler is no longer supported. Use onOpenChange to detect when the modal dialog closes.

  • status prop is no longer supported. Use the UNSTABLE_AlertDialog component if you need an overlay with a specific tone.

  • closeButton prop is no longer supported. If the modal dialog is dismissible, a close button will be rendered automatically. If the modal dialog is not dismissible, you must provide a way for the user to close the modal dialog. This can be done by passing a function as children to the ModalDialog component, and using the close function provided as an argument to that function.

    // Before
    <Modal closeOnOutsideClick={false} closeButton={true}>
    Modal content
    </Modal>
    // After
    <ModalDialog isDismissible={false}>
    {({ close }) => (
    <>
    <ModalDialogBody>
    Modal content
    </ModalDialogBody>
    <ModalDialogActions>
    <Button onPress={close}>Close</Button>
    </ModalDialogActions>
    </>
    )}
    </ModalDialog>

In most cases, the NavTab component should be migrated to the LinkTabs component. Similarly, the NavTabItem component should be migrated to the LinkTab component.

  • In Component Library, NavTabItem allowed all kinds of child elements within the tab. In Cimpress UI, the LinkTab component is itself interactive, and doesn’t allow nesting other components within it. A restricted set of additional content can be added to the tab using the iconStart and badge props.

    // Before
    <NavTab>
    <NavTabItem>
    <button>Option 1</button>
    </NavTabItem>
    <NavTabItem>
    <button>Option 2</button>
    </NavTabItem>
    </NavTab>
    // After
    <LinkTabs>
    <LinkTab>Option 1</LinkTab>
    <LinkTab>Option 2</LinkTab>
    </LinkTabs>
  • vertical prop is no longer supported.

  • disabled prop on NavTabItem has been renamed to isDisabled on LinkTab.

  • active prop on NavTabItem is no longer supported. Use currentHref or defaultHref prop on LinkTabs instead, in combination with href on each LinkTab.

    // Before
    const [selectedTab, setSelectedTab] = useState('tab-1');
    return (
    <NavTab>
    <NavTabItem active={selectedTab === 'tab-1'}>
    <button onClick={() => setSelectedTab('tab-1')}>Tab 1</button>
    </NavTabItem>
    <NavTabItem active={selectedTab === 'tab-2'}>
    <button onClick={() => setSelectedTab('tab-2')}>Tab 2</button>
    </NavTabItem>
    </NavTab>
    );
    // After
    const [selectedTab, setSelectedTab] = useState('#tab-1');
    return (
    <LinkTabs currentHref={selectedTab} onHrefChange={setSelectedTab}>
    <LinkTab href="#tab-1">Tab 1</LinkTab>
    <LinkTab href="#tab-2">Tab 2</LinkTab>
    </LinkTabs>
    );
  • LinkTabs now requires a hidden label to provide context about its purpose to assistive technologies. This label can be provided using the aria-label or aria-labelledby props.

In most cases, the Radio component should be migrated to the Radio component.

  • Radios cannot be used on their own, and must always be wrapped in a <RadioGroup> component.

  • disabled prop has been renamed to isDisabled.

  • value prop is now required. value must be unique across all radios within a RadioGroup. This prop only accepts string content.

  • name prop is no longer supported. Use the name prop on RadioGroup instead.

  • checked prop is no longer supported. To make a radio button selected, provide its value to the value/defaultValue prop on RadioGroup.

    // Before
    <Radio value="option-1" />
    <Radio value="option-2" checked />
    // After
    <RadioGroup value="option-2">
    <Radio value="option-1">...</Radio>
    <Radio value="option-2">...</Radio>
    </RadioGroup>
  • onCheck event handler is no longer supported. Use onChange on RadioGroup instead.

  • There is no label prop on Radio. Instead, provide the radio label as children of the Radio component. Note that nesting other components within Radio is not allowed.

    // Before
    <Radio label="Option 1" />
    // After
    <Radio>Option 1</Radio>

    If the radio is rendered without a visible label, a hidden label must still be provided to identify the radio’s purpose to assistive technologies. This can be done using the aria-label or aria-labelledby props.

  • backgroundColor prop is no longer supported. The radio indicator will always render with a white background.

  • onFocus and onBlur event handlers are no longer supported.

In most cases, the RadioGroup component should be migrated to the RadioGroup component.

  • The following props have been renamed:

    • valueSelected becomes value
    • defaultSelected becomes defaultValue

    Both of these props now only accept string content.

  • The inline prop has been replaced with a direction prop. This prop accepts one of two values: 'vertical' and 'horizontal'.

    // Before
    <RadioGroup inline>
    ...
    </RadioGroup>
    // After
    <RadioGroup direction="horizontal">
    ...
    </RadioGroup>
  • onChange event handler now accepts a single string argument - the value of the currently selected option.

    // Before
    <RadioGroup
    onChange={(e, value) => console.log(`Selected option: ${value}`)}
    >
    ...
    </RadioGroup>
    // After
    <RadioGroup
    onChange={(value) => console.log(`Selected option: ${value}`)}
    >
    ...
    </RadioGroup>
  • RadioGroup now requires a label to provide context about its purpose. This can either be a visible label (provided using the label prop) or a hidden one (provided using the aria-label or aria-labelledby props).

In most cases, the Select component should be migrated to one of the following components:

  • Select (for single selection without filtering)
  • ComboBox (for single selection with filtering)
  • TagField (for multiple selection with filtering)
  • isMulti and isSearchable props are no longer supported. Instead, choose an appropriate component to migrate to from the list above.

  • The following props have been renamed:

    • required becomes isRequired
    • onChange becomes onSelectionChange
    • helpText becomes description
  • description and placeholder props now only accept string content, and nesting other components is not allowed.

  • status prop is no longer supported. To show an error state, use the isInvalid prop.

  • options prop is no longer supported. Options can now be provided as a static list using the children prop, or as a dynamic list using the items prop.

    When providing a static list, use the appropriate <...Item> and <...Section> components. For example, if migrating to a ComboBox component, you would use ComboBoxItem and ComboBoxSection components to define your list. You can also use the <Divider> component to render a divider between items.

    // Before
    const options = [
    { label: 'Option 1', value: 'option-1' },
    { label: 'Option 2', value: 'option-2' },
    ];
    return <Select options={options} onChange={(option) => console.log(`${option.value} selected`)} />;
    // After
    return (
    <Select onSelectionChange={(id) => console.log(`${id} selected`)}>
    <SelectItem id="option-1">Option 1</SelectItem>
    <SelectItem id="option-2">Option 2</SelectItem>
    </Select>
    );

    When providing a dynamic list, use the items prop to provide the array describing your list. You will also have to instruct the component how to render your list items by providing a mapping function using the children prop.

    // Before
    const options = [
    { label: 'Option 1', value: 'option-1' },
    { label: 'Option 2', value: 'option-2' },
    ];
    return <Select options={options} />;
    // After
    const items = [
    { id: 'option-1', label: 'Option 1' },
    { id: 'option-2', label: 'Option 2' },
    ];
    return <Select items={items}>{(item) => <SelectItem>{item.label}</SelectItem>}</Select>;

    See our collection components guide for more information and examples.

  • value and defaultValue props have been renamed to selectedKey and defaultSelectedKey respectively. These props now accept the ID of the selected item instead of the selected item itself. For TagField, the prop names are pluralized, and they accept a set of IDs.

  • onSelectionChange event handler now accepts a single argument: the ID of the selected item (a set of IDs for TagField).

  • containerClassName prop is no longer supported, as Cimpress UI doesn’t allow style overrides.

  • For imperative actions like focusing or text selection, use the apiRef prop instead of ref.

    // Before
    const ref = useRef<HTMLElement>(null);
    return (
    <Select ref={ref} />
    <Button onClick={() => ref.current?.focus()}>Focus</Button>
    );
    // After
    const apiRef = useRef<SelectApi>(null);
    return (
    <Select apiRef={apiRef} />
    <Button onPress={() => apiRef.current?.focus()}>Focus</Button>
    );
  • The old Select component exposed implementation details of an external library, and most of those props are no longer supported. Read through the API documentation of your chosen replacement component to see its capabilities and to find alternatives for props not listed in this guide.

  • Select, ComboBox, and TagField all require a label to provide context about their purpose. This can either be a visible label (provided using the label prop) or a hidden one (provided using the aria-label or aria-labelledby props).

The SelectWrapper component was used to provide Component Library styling to forks of the react-select component. This is no longer supported in Cimpress UI, and usages of SelectWrapper should be migrated to one of the following components:

  • Select (for single selection without filtering)
  • ComboBox (for single selection with filtering)
  • TagField (for multiple selection with filtering)

Follow the Select migration guide together with the usage guidelines linked above to aid the migration.

In most cases, the Spinner component should be migrated to the Spinner component.

  • Spinner now requires a label to provide context about its purpose. This can either be a visible label (provided using the label prop) or a hidden one (provided using the aria-label or aria-labelledby props).

  • fullPage prop is no longer supported. An equivalent layout can be achieved by rendering a spinner in the center of an absolutely positioned container.

  • duration, offset, and handleOffset props are no longer supported. Customization of the spinner animation is not allowed.

In most cases, the TabCard component should be migrated to the Tabs component.

  • There are now multiple components that need to be composed to render tabs.

    // Before
    <TabCard tabs="..." />
    // After
    <Tabs>
    <TabList>
    ...
    </TabList>
    <TabPanels>
    ...
    </TabPanels>
    </Tabs>
  • tabs prop is no longer supported. Tabs can now be provided as a static or dynamic list.

    When providing a static list, use the <Tab> component to define a tab inside <TabList>, and the <TabPanel> component to define the tab contents inside <TabPanels>. You will also need to associate each <Tab> with its <TabPanel> by providing the same value for the id prop.

    // Before
    const tabs = [
    { name: 'Tab 1', block: 'Content for tab 1' },
    { name: 'Tab 2', block: 'Content for tab 2' },
    ];
    return <TabCard tabs={tabs} />;
    // After
    return (
    <Tabs>
    <TabList>
    <Tab id="tab-1">Tab 1</Tab>
    <Tab id="tab-2">Tab 2</Tab>
    </TabList>
    <TabPanels>
    <TabPanel id="tab-1">Content for tab 1</TabPanel>
    <TabPanel id="tab-2">Content for tab 2</TabPanel>
    </TabPanels>
    </Tabs>
    );

    When providing a dynamic list, use the items prop on <TabList> and <TabPanels> to provide the array describing your list. You will also have to instruct the components how to render your list items by providing a mapping function using the children prop.

    // Before
    const tabs = [
    { name: 'Tab 1', block: 'Content for tab 1' },
    { name: 'Tab 2', block: 'Content for tab 2' },
    ];
    return <TabCard tabs={tabs} />;
    // After
    const tabs = [
    { id: 'tab-1', title: 'Tab 1', content: 'Content for tab 1' },
    { id: 'tab-2', title: 'Tab 2', content: 'Content for tab 2' },
    ];
    return (
    <Tabs>
    <TabList items={tabs}>{(item) => <Tab>{item.title}</Tab>}</TabList>
    <TabPanels items={tabs}>{(item) => <TabPanel>{item.content}</TabPanel>}</TabPanels>
    </Tabs>
    );

    See our collection components guide for more information and examples.

  • selectedIndex prop has been renamed to selectedKey. This prop now accepts the ID of the selected tab instead of its index in the tabs array.

    // Before
    const tabs = [
    { name: 'Tab 1', block: 'Content for tab 1' },
    { name: 'Tab 2', block: 'Content for tab 2' },
    ];
    return <TabCard tabs={tabs} selectedIndex={0} />;
    // After
    const tabs = [
    { id: 'tab-1', title: 'Tab 1', content: 'Content for tab 1' },
    { id: 'tab-2', title: 'Tab 2', content: 'Content for tab 2' },
    ];
    return <Tabs selectedKey="tab-1">...</Tabs>;

    A new defaultSelectedKey prop was also added to allow for uncontrolled usage.

  • onSelect event handler has been renamed to onSelectionChange. It now accepts a single argument: the ID of the selected tab.

    // Before
    const tabs = [
    { name: 'Tab 1', block: 'Content for tab 1' },
    { name: 'Tab 2', block: 'Content for tab 2' },
    ];
    return <TabCard tabs={tabs} onSelect={(e, selectedIndex) => console.log(`Selected tab index: ${selectedIndex}`)} />;
    // After
    const tabs = [
    { id: 'tab-1', title: 'Tab 1', content: 'Content for tab 1' },
    { id: 'tab-2', title: 'Tab 2', content: 'Content for tab 2' },
    ];
    return <Tabs onSelectionChange={(selectedId) => console.log(`Selected tab ID: ${selectedId}`)}>...</Tabs>;
  • Tab footers are no longer supported.

  • The href attribute on tabs is no longer supported. If you’re using tabs as your application’s primary method of navigation, we recommend using the <TopNav> component instead. For other use cases, you can use the <LinkTabs> component instead.

  • Tabs now requires a hidden label to provide context about its purpose to assistive technologies. This label can be provided using the aria-label or aria-labelledby props.

In most cases, the Tag component should be migrated to the Tag component.

  • Tags cannot be used on their own, and must always be wrapped in a <TagGroup> component. A standalone <Tag> component will throw an error at runtime.

  • Values for the size prop are different:

    • 'sm' and 'default' should be replaced with 'medium'
    • 'lg' should be replaced with 'large'
  • There is no label prop on Tag. Instead, provide the tag contents as children of the Tag component. Note that nesting other components within Tag is not allowed.

// Before
<Tag label="My tag" />
// After
<Tag>My tag</Tag>

Additionally, in Component Library it was possible to omit label if value was provided. This is no longer the case, and tag content must always be explicitly provided.

  • value prop has been renamed to id. Note that only string content is accepted, and nesting other components is not allowed.

  • variant prop is no longer supported.

  • removable and onRemoveClick props are no longer supported. To allow tags to be removed, use the onRemove event handler on the parent <TagGroup> component.

In most cases, the TextArea component should be migrated to the TextArea component.

  • The following props have been renamed:

    • required becomes isRequired
    • disabled becomes isDisabled
    • helpText becomes description
  • description prop now only accepts string content, and nesting other components is not allowed.

  • status prop is no longer supported. To show an error state, use the isInvalid prop.

  • inputStyle prop is no longer supported, as Cimpress UI doesn’t allow style overrides.

  • rightAddon prop is no longer supported. Additional elements should be rendered outside of the text area.

  • type prop is no longer supported, as it never had any effect on TextArea.

  • pattern prop is no longer supported. Use the pattern prop on TextField instead.

  • For imperative actions like focusing or text selection, use the apiRef prop instead of ref.

    // Before
    const ref = useRef<HTMLTextAreaElement>(null);
    return (
    <TextArea ref={ref} />
    <Button onClick={() => ref.current?.focus()}>Focus</Button>
    );
    // After
    const apiRef = useRef<TextAreaApi>(null);
    return (
    <TextArea apiRef={apiRef} />
    <Button onPress={() => apiRef.current?.focus()}>Focus</Button>
    );
  • onChange event handler now accepts a single string argument - the current contents of the text area.

    // Before
    <TextArea
    label="..."
    onChange={(e) => console.log(`Input value: ${e.target.value}`)}
    />
    // After
    <TextArea
    label="..."
    onChange={(value) => console.log(`Input value: ${value}`)}
    />
  • onClick event handler is no longer supported. Use onFocus and/or onChange to detect when the user interacts with the field.

  • In Component Library, TextArea required either a label or a placeholder to be provided. In Cimpress UI, a label is now required to provide context about the field’s purpose. This can either be a visible label (provided using the label prop) or a hidden one (provided using the aria-label or aria-labelledby props). The placeholder prop is independent from label, and is optional.

In most cases, the TextField component should be migrated to one of the following components:

  • The following props have been renamed:

    • required becomes isRequired
    • disabled becomes isDisabled
    • helpText becomes description
  • description prop now only accepts string content, and nesting other components is not allowed.

  • status prop is no longer supported. To show an error state, use the isInvalid prop.

  • inputStyle prop is no longer supported, as Cimpress UI doesn’t allow style overrides.

  • rightAddon prop is no longer supported. Use the suffix prop to render a piece of text or a single icon at the end of the input field. Interactive elements should be rendered outside of the field.

  • For imperative actions like focusing or text selection, use the apiRef prop instead of ref.

    // Before
    const ref = useRef<HTMLInputElement>(null);
    return (
    <TextField ref={ref} />
    <Button onClick={() => ref.current?.focus()}>Focus</Button>
    );
    // After
    const apiRef = useRef<TextFieldApi>(null);
    return (
    <TextField apiRef={apiRef} />
    <Button onPress={() => apiRef.current?.focus()}>Focus</Button>
    );
  • onChange event handler now accepts a single string argument - the current contents of the input field.

    // Before
    <TextField
    label="..."
    onChange={(e) => console.log(`Input value: ${e.target.value}`)}
    />
    // After
    <TextField
    label="..."
    onChange={(value) => console.log(`Input value: ${value}`)}
    />
  • onInput event handler is no longer supported, use onChange instead.

  • onClick event handler is no longer supported. Use onFocus and/or onChange to detect when the user interacts with the field.

  • In Component Library, TextField required either a label or a placeholder to be provided. In Cimpress UI, a label is now required to provide context about the field’s purpose. This can either be a visible label (provided using the label prop) or a hidden one (provided using the aria-label or aria-labelledby props). The placeholder prop is independent from label, and is optional.

In most cases, the Tooltip component should be migrated to one of the following components:

  • Tooltip (if it contains no interactive content, and is shown on hover/focus)
  • Popover (if it contains interactive content, and is shown on press)
  • The following props have been renamed:

    • show becomes isOpen
    • direction becomes placement
  • tooltipStyle, tooltipInnerStyle, and containerClassName props are no longer supported, as Cimpress UI doesn’t allow style overrides.

  • constraints prop is no longer supported, as it was exposing an implementation detail of an external library that is no longer being used.

  • variant, variety, delay and outsideClickIgnoreClass props are no longer supported.

  • onClickOutside prop is no longer supported. Use onOpenChange event handler to detect when the overlay closes.

  • If migrating to Tooltip: contents prop has been renamed to label, and it now only accepts string content. Nesting other components is not allowed.

  • If migrating to Popover: the trigger button and the popover contents are now provided differently. Provide popover contents as children of the <Popover> component, and wrap both the trigger button and the popover itself in the <PopoverRoot> component. Make sure that the trigger button is provided first.

    This also means that the contents prop is no longer supported.

    // Before
    <Tooltip variant="popover" contents={<p>This is a <b>popover</b>!</p>}>
    <button>Show popover</button>
    </Tooltip>
    // After
    <PopoverRoot>
    <Button>Show popover</Button>
    <Popover>
    <p>This is a <b>popover</b>!</p>
    </Popover>
    </PopoverRoot>
  • If migrating to Popover: the Popover component requires a title to identify its purpose to assistive technologies. This can either be a visible title (provided using the title prop) or a hidden one (provided using the aria-label or aria-labelledby props).