Skip to content
Enact

Migrating to Enact 5.0

This document lists changes between Enact versions 4.5 and 5.0 likely to affect most apps. If you are coming from Enact 3.x, please migrate to 4.0 and then consult this guide.

Enact 5.0 updates the react and react-dom dependencies to 19.x. Developers should ensure their code does not rely on features that are no longer available in these versions. React 19 introduces Actions, new API use, ref as a prop, Context as a provider and some deprecated APIs.

Actions in React 19 are new features designed to handle asynchronous transitions automatically, such as submitting a form or fetching data.

For example, you can use useTransition to handle the pending state for you:

function Button() {
const [isPending, startTransition] = useTransition();
const handleClick = () => {
startTransition(async () => {
await new Promise(() => {
setTimeout(resolve, 3000);
});
console.log('Async job done');
});
};
return (
<button onClick={handleClick} disabled={isPending}>
{isPending ? 'Loading' : 'Click Me'}
</button>
);
}

The async transition will immediately set the isPending state to true, make the async request(s), and then switch isPending to false after any transitions. This allows you to keep the current UI responsive and interactive while the data is changing.

Please take a look at the new hooks for Actions, such as useActionState or useOptimistic, too.

The use API can be used in a component to read the value of a Promise or context When used with a Promise, React will suspend until the promise resolves. When use is used to read context, use can be called inside loops or conditional statements unlike useContext.

const CounterContext = createContext(0);
function MyCounter() {
const count = use(CounterContext);
return <div>Count: {count}</div>;
}

In React 19, you can now access ref as a prop for function components:

function MyComponent({ref}) {
return <button ref={ref}>Button</button>;
}
// Usage
<MyComponent ref={ref} />;

Function components no longer need forwardRef to expose a DOM node to a parent component. forwardRef will be deprecated and removed in future versions.

<Context> can now be rendered as a provider. Note that <Context.Provider> will be deprecated in future versions.

const CounterContext = createContext(0);
function App({children}) {
return <CounterContext value={5}>{children}</CounterContext>;
}

Removed deprecated React APIs and React DOM APIs

Section titled “Removed deprecated React APIs and React DOM APIs”
  • propTypes and defaultProps for functions: These are removed. Using propType will be silently ignored. Use ES6 default parameters instead of defaultProps. Class components will continue to support defaultProps.
  • ReactDOM.findDOMNode: This API is removed. Replace it with DOM refs.
  • react-dom/test-utils: Import act from react instead of react-dom/test-utils.

For more details, please refer to React 19 Upgrade Guide and React v19 from React Blog.

@enact/cli must be upgraded to version 7.0.0 or newer, as shown below.

Terminal window
npm install -g @enact/cli@next

@enact/cli updates the eslint to 9.x, jest to 29.x, and react, react-dom to 19.x. Developers should ensure their code does not rely on features that are no longer available in these versions.

As @enact/cli updates the minimum version of Node to 20.10.0, please check your development environment before installing @enact/cli.

As we update to eslint 9, some of the lint rules could be changed. If you run into unknown lint warnings or errors, don’t be afraid, and please proceed to fix them. They are likely to be the rules from eslint-plugin-react, so refer to the console message and look up which rule is related.

Enact 5.0 no longer supports the 2025 TV platform or earlier versions.

We’ve changed the default theme for webOS TV from sandstone to limestone. If you would like to migrate your existing sandstone app, please follow the steps below.

  1. Update your package.json
...
"enact": {
"theme": "limestone"
},
...
"dependenceis": {
"@enact/core": "^5.0.0",
"@enact/i18n": "^5.0.0",
"@enact/limestone": "enactjs/limestone#1.0.0",
"@enact/spotlight": "^5.0.0",
"@enact/ui": "^5.0.0",
...
}
  1. Update the paths for importing components and any sand prefix to lime
// sandstone
import Button from `@enact/sandstone/Button`
// limestone
import Button from `@enact/limestone/Button`
  1. Delete your npm-shrinkwrap.json file and install the dependencies
Terminal window
$ rm -rf npm-shrinkwrap.json
$ npm install
$ npm shrinkwrap

Limestone now uses the Enova design-tokens system to style its components, importing semantic tokens from @enovaui/webos-tokens in the Enova design-tokens repository. This enables consistent and customizable styling across all components.

With the removal of the ReactDOM.findDOMNode API, ContextualPopupDecorator and Dropdown have been changed to include a sibling DOM node by using @enact/core/internal/WithRef to access DOM nodes.

Due to introducing @enact/core/internal/WithRef HoC, one of your apps can face unexpected issues. We have found 2 sample cases that you should know in advance.

Make sure your component wrapped with ContextualPopupDecorator passes the rest props to the inner component to get the proper ref, as shown below.

const SampleButton = kind({
render: (props) => {
const {icon, ...rest} = props;
return (
<Icon
{...rest} // Ensure the inner component receives the `rest` props
//...
>{icon}</Icon>
);
}
});
const ContextualSampleButton = ContextualPopupDecorator(SampleButton);

Make sure the root element of your Panel component inside Panels is not a <> (fragment). It will result in incorrect panel transition animations.

const SamplePanels = kind({
render: (props) => {
return (
<Panels>
<Panel>
<> {/* This should be changed to <div> */}
<ComponentA />
<ComponentB />
</>
</Panel>
</Panels>
);
}
});

The prop overlayPosition is added to set the position of the alert when the type is overlay.

The prop bordered and centered are added.

The new component Card is added.

The new component Chip is added.

The new component Chips is added.

offset prop now accepts a large value.

offset prop now accepts a large value.

The default value of the size prop is tiny.

New icons are added.

The prop wideImage is added. The props children and label accept a Node type value.

The prop buttons is added.

The image-based keyGuide is added.

The Switch icon is now drawn using CSS instead of an icon font.

Tabs in horizontal TabLayout now support scrolling. The prop primaryIndex is added to focus primary tab when pressing back key from other tabs or initial rendering.

The prop includeTimeHour is added to conditionally show hour for current and total time

The subtitle of WizardPanels now supports a marquee effect.

Various styling changes have been made to support new UI/UX designs for the webOS TV platform.

The table below shows which less variables in sandstone match which variable or value in limestone.

SandstoneLimestone
@sand-bg-colorvar(--semantic-color-background-full-default)
@sand-text-colorvar(--semantic-color-on-background-main)
@sand-text-sub-colorvar(--semantic-color-on-background-sub)
@sand-shadow-base-colorvar(--semantic-color-on-background-black) with alpha 0.35
@sand-component-text-colorvar(--semantic-color-on-surface-main)
@sand-component-text-sub-colorvar(--semantic-color-on-surface-sub)
@sand-component-bg-colorvar(--semantic-color-surface-default)
@sand-component-active-indicator-bg-colorvar(--semantic-color-surface-default-indicator)
@sand-component-inactive-indicator-bg-colorvar(--semantic-color-surface-default-indicator) with alpha 0.4
@sand-focus-text-colorvar(--semantic-color-on-surface-main-focused)
@sand-focus-bg-colorvar(--semantic-color-surface-default-focused)
@sand-component-focus-text-colorvar(--semantic-color-on-surface-main-focused)
@sand-component-focus-active-indicator-bg-colorvar(--semantic-color-on-surface-main-focused)
@sand-component-focus-inactive-indicator-bg-colorvar(--semantic-color-on-surface-main-focused) with alpha 0.4
@sand-selected-colorvar(--semantic-color-on-surface-main-selected)
@sand-selected-text-colorvar(--semantic-color-on-surface-main-selected)
@sand-selected-bg-colorvar(--semantic-color-surface-default-selected)
@sand-disabled-focus-bg-colorvar(--semantic-color-surface-default-disabled-focused)
@sand-disabled-selected-colorvar(--semantic-color-on-surface-main-selected)
@sand-disabled-selected-bg-colorvar(--semantic-color-surface-accent)
@sand-disabled-selected-focus-colorvar(--semantic-color-on-surface-main-selected)
@sand-disabled-selected-focus-bg-colorvar(--semantic-color-surface-accent)
@sand-fullscreen-bg-colorvar(--semantic-color-background-full-default)
@sand-overlay-text-shadow0 4px 4px var(--semantic-color-on-background-black) with alpha 0.35
@sand-overlay-bg-colorvar(--semantic-color-background-overlay-default)
@sand-selection-color@lime-input-selection-color
@sand-selection-bg-color@lime-input-selection-bg-color
@sand-toggle-off-color@lime-switch-icon-bg-color
@sand-toggle-off-bg-color@lime-switch-bg-color
@sand-toggle-on-color@lime-switch-selected-icon-bg-color
@sand-toggle-on-bg-color@lime-switch-selected-bg-color
@sand-progress-colorvar(--semantic-color-on-surface-accent)
@sand-progress-bg-colorvar(--semantic-color-surface-default-track)
@sand-progress-bg-color-opacity1
@sand-progress-highlighted-color@lime-progressbar-highlighted-fill-bg-color
@sand-progress-slider-colorvar(--semantic-color-on-surface-accent)
@sand-invalid-sub-colorvar(--semantic-color-on-background-default-error)
@sand-checkbox-colorvar(--semantic-color-on-background-main)
@sand-keyguide-bg-base-colorvar(--semantic-color-background-overlay-default)
@sand-keyguide-bg-color-opacity1
@sand-alert-overlay-text-color-rgb@lime-alert-overlay-label-color
@sand-alert-overlay-text-sub-color@lime-alert-overlay-label-sub-color
@sand-button-text-color@lime-button-fill-label-color
@sand-button-bg-color@lime-button-fill-container-color
@sand-button-border-color@lime-button-outline-border-color
@sand-button-focus-bg-color@lime-button-fill-container-focused-color
@sand-button-selected-text-color@lime-button-fill-label-selected-color
@sand-button-selected-bg-color@lime-button-fill-container-selected-color
@sand-button-focus-filter@lime-button-filter-focused
@sand-button-selected-filter@lime-button-filter-selected
@sand-button-transparent-text-color@lime-button-transparent-label-color
@sand-button-focus-icononly-bg-color@lime-button-icononly-bg-focused-color
@sand-button-text-shadow@lime-button-text-shadow
@sand-checkbox-text-color@lime-checkbox-icon-color
@sand-checkbox-bg-color@lime-checkbox-container-color
@sand-checkbox-border-color@lime-checkbox-border-color
@sand-checkbox-disabled-selected-text-color@lime-checkbox-icon-selected-disabled-color
@sand-checkbox-disabled-selected-bg-color@lime-checkbox-container-selected-disabled-color
@sand-checkbox-disabled-selected-border-color@lime-checkbox-border-selected-disabled-color
@sand-checkbox-focus-disabled-selected-text-color@lime-checkbox-icon-selected-disabled-focused-color
@sand-checkbox-focus-disabled-selected-bg-color@lime-checkbox-container-selected-disabled-focused-color
@sand-checkbox-focus-disabled-selected-border-color@lime-checkbox-border-selected-disabled-focused-color
@sand-checkbox-focus-text-color@lime-checkbox-icon-focused-color
@sand-checkbox-focus-bg-color@lime-checkbox-container-focused-color
@sand-checkbox-focus-border-color@lime-checkbox-border-focused-color
@sand-checkbox-selected-text-color@lime-checkbox-icon-selected-color
@sand-checkbox-selected-bg-color@lime-checkbox-container-selected-color
@sand-checkbox-selected-border-color@lime-checkbox-border-selected-color
@sand-checkbox-selected-focus-text-color@lime-checkbox-icon-selected-focused-color
@sand-checkbox-selected-focus-bg-color@lime-checkbox-container-selected-focused-color
@sand-checkbox-selected-focus-border-color@lime-checkbox-border-selected-focused-color
@sand-checkbox-indeterminate-text-color@lime-checkbox-icon-indeterminate-color
@sand-checkbox-indeterminate-bg-color@lime-checkbox-container-indeterminate-color
@sand-checkbox-indeterminate-border-color@lime-checkbox-border-indeterminate-color
@sand-checkbox-indeterminate-focus-text-color@lime-checkbox-icon-indeterminate-focused-color
@sand-checkbox-indeterminate-focus-bg-color@lime-checkbox-container-indeterminate-focused-color
@sand-checkbox-indeterminate-focus-border-color@lime-checkbox-border-indeterminate-focused-color
@sand-checkbox-standalone-bg-disabled-focus-bg-color@lime-checkbox-container-standalone-disabled-focused-color
@sand-contextualpopup-bg-color@lime-contextualpopup-container-color
@sand-dropdown-selected-text-color@lime-dropdown-label-selected-color
@sand-heading-text-color@lime-heading-label-main-color
@sand-heading-subtitle-text-color@lime-heading-label-sub-color
@sand-heading-border-color@lime-heading-border-color
@sand-flexiblepopuppanels-panel-bg-color@lime-flexiblepopuppanels-panel-bg-color
@sand-fixedpopuppanels-bg-color@lime-fixedpopuppanels-background-color
@sand-fixedpopuppanels-shadow@lime-overlay-shadow
@sand-formcheckboxitem-focus-text-color@lime-formcheckboxitem-label-main-focused-color
@sand-iconitem-label-color@lime-iconitem-inner-label-color
@sand-iconitem-label-dark-color@lime-iconitem-inner-label-dark-color
@sand-iconitem-label-dark-color@lime-iconitem-inner-label-dark-color
@sand-iconitem-border-color@lime-iconitem-inner-border-color
@sand-imageitem-caption-color@lime-imageitem-label-main-color
@sand-imageitem-label-colorlime-imageitem-label-sub-color
@sand-imageitem-selected-selection-icon-bg-color@lime-imageitem-selection-container-selected-color
@sand-imageitem-selected-selection-icon-color@lime-imageitem-selection-icon-selected-color
@sand-item-label-color@lime-item-label-sub-color
@sand-item-focus-label-color@lime-item-label-sub-focused-color
@sand-item-disabled-focus-bg-color@lime-item-container-disabled-focused-color
@sand-inputfield-text-color@lime-inputfield-label-inactive-color
@sand-inputfield-placeholder-color@lime-inputfield-label-inactive-color
@sand-inputfield-placeholder-active-color@lime-inputfield-label-inactive-focused-color
@sand-inputfield-focus-text-color@lime-inputfield-label-active-color
@sand-inputfield-disabled-text-color@lime-inputfield-label-inactive-color
@sand-inputfield-invalid-color@lime-inputfield-invalid-color
@sand-inputfield-invalid-tooltip-color@lime-inputfield-invalid-tooltip-color
@sand-item-label-color@lime-item-label-sub-color
@sand-item-focus-label-color@lime-item-label-sub-focused-color
@sand-keyguide-bg-color@lime-keyguide-container-color
sand-keyguide-item-color@lime-keyguide-label-color
@sand-picker-text-color@lime-picker-label-color
@sand-picker-indicator-active-bg-color@lime-picker-indicator-color
@sand-picker-indicator-active-focus-bg-color@lime-picker-indicator-focused-color
@sand-picker-indicator-inactive-bg-color@lime-picker-indicator-color with alpha 0.4
@sand-picker-indicator-inactive-focus-bg-color@lime-picker-indicator-focused-color with alpha 0.4
@sand-picker-joined-horizontal-text-color@lime-picker-joined-label-color
@sand-picker-joined-horizontal-bg-color@lime-picker-container-color
@sand-popuptablayout-bg-color@lime-popuptablayout-content-background-color
@sand-progressbar-bar-bg-color@lime-progressbar-track-color
@sand-progressbar-load-bg-color@lime-progressbar-track-buffer-color
@sand-progressbar-fill-bg-color@lime-progressbar-track-active-color
@sand-progressbar-highlighted-fill-bg-color@lime-progressbar-track-active-focused-color
@sand-progressbutton-bar-color@lime-progressbutton-track-color
@sand-progressbutton-focus-fill-color@lime-progressbutton-track-active-focused-color
@sand-radioitem-indicator-color@lime-radioitem-icon-color
@sand-radioitem-indicator-bg-color@lime-radioitem-container-color
@sand-radioitem-indicator-border-color@lime-radioitem-border-color
@sand-radioitem-focus-indicator-color@lime-radioitem-icon-focused-color
@sand-radioitem-focus-indicator-bg-color@lime-radioitem-container-focused-color
@sand-radioitem-focus-indicator-border-color@lime-radioitem-border-focused-color
@sand-radioitem-selected-indicator-color@lime-radioitem-icon-selected-color
@sand-radioitem-selected-indicator-bg-color@lime-radioitem-container-selected-color
@sand-radioitem-selected-indicator-border-color@lime-radioitem-border-selected-color
@sand-radioitem-selected-focus-indicator-color@lime-radioitem-icon-selected-focused-color
@sand-radioitem-selected-focus-indicator-bg-color@lime-radioitem-container-selected-focused-color
@sand-radioitem-selected-focus-indicator-border-color@lime-radioitem-border-selected-focused-color
@sand-spinner-text-color@lime-spinner-label-color
@sand-steps-pageindicator-current-bg-color@lime-steps-indicator-icon-active-color
@sand-switch-color@lime-switch-handle-inactive-color
@sand-switch-bg-color@lime-switch-container-inactive-color
@sand-switch-focus-color@lime-switch-handle-inactive-focused-color
@sand-switch-selected-color@lime-switch-handle-active-color
@sand-switch-selected-bg-color@lime-switch-container-active-color
@sand-switch-selected-focus-color@lime-switch-handle-active-focused-color
@sand-switch-disabled-selected-color@lime-switch-handle-active-disabled-color
@sand-switch-disabled-selected-bg-color@lime-switch-container-active-disabled-color
@sand-scrollbar-track-bg-color@lime-scroll-track-color
@sand-scrollbar-thumb-bg-color@lime-scroll-handle-color
@sand-scrollbar-thumb-focus-bg-color@lime-scroll-handle-focused-color
@sand-scrollbar-thumb-focus-direction-indicator-color@lime-scroll-arrow-color
@sand-scrollbar-thumb-focus-box-shadow-color@lime-scrollbar-thumb-focus-box-shadow-color
@sand-slider-bar-bg-color@lime-slider-track-color
@sand-slider-fill-bg-color@lime-slider-track-active-color
@sand-slider-load-bg-color@lime-slider-track-buffer-color
@sand-slider-knob-bg-color@lime-slider-handle-color
@sand-slider-knob-border-color@lime-slider-handle-border-color
@sand-slider-focus-knob-bg-color@lime-slider-handle-focused-color
@sand-slider-focus-knob-border-color@lime-slider-handle-focused-border-color
@sand-slider-focus-knob-shadow@lime-slider-handle-focused-shadow
@sand-tooltip-bg-color@lime-tooltip-container
@sand-tooltip-text-color@lime-tooltip-label
@sand-video-player-title-color@lime-video-player-label-color
  • @lime-alert-full-bg-color
  • @lime-button-fill-container-disabled-focused-color, @lime-button-fill-icon-color, @lime-button-fill-icon-selected-color, @lime-button-fill-label-focused-color, @lime-button-outline-container-color, @lime-button-outline-container-focused-color, @lime-button-outline-container-selected-color, @lime-button-outline-border-selected-color, @lime-button-outline-label-color, @lime-button-outline-label-focused-color, @lime-button-outline-label-selected-color, @lime-button-transparent-container-focused-color, @lime-button-transparent-container-selected-color, @lime-button-transparent-label-focused-color, @lime-button-transparent-icon-color, @lime-button-transparent-icon-focused-color, @lime-button-transparent-icon-selected-color, @lime-button-transparent-label-selected-color.
  • @lime-checkboxitem-item-container-selected-color.
  • @lime-contextualpopup-border-color.
  • @lime-datetime-label-color.
  • @lime-heading-label-tiny-color.
  • @lime-formcheckboxitem-label-sub-focused-color.
  • @lime-iconitem-inner-icon-color, @lime-iconitem-inner-container-color, @lime-iconitem-outer-label-main-color.
  • @lime-input-button-popup-transparent-label-color, @lime-input-button-popup-transparent-icon-color, @lime-input-fullscreen-dot-color, @lime-input-header-popup-label-main-color, @lime-input-header-popup-label-sub-color, @lime-input-inputfield-popup-container-color, @lime-input-inputfield-popup-label-inactive-color, @lime-input-overlay-dot-color.
  • @lime-inputfield-container-color, @lime-inputfield-container-disabled-focused-color, @lime-inputfield-cursor-color, @lime-inputfield-label-success-color.
  • @lime-imageitem-full-image-border-color,@lime-imageitem-label-main-focused-color, @lime-imageitem-label-main-selected-color,@lime-imageitem-label-sub-focused-color, @lime-imageitem-label-sub-selected-color.
  • @lime-item-label-main-selected-color, @lime-item-label-sub-selected-color, @lime-item-container-selected-color, @lime-item-container-disabled-focused-color.
  • @lime-keyguide-icon-color.
  • @lime-mediaoverlay-inner-border-focused.
  • @lime-overlay-shadow.
  • @lime-pageviews-bg-color, @lime-pageviews-indicator-icon-active, @lime-pageviews-indicator-icon-inactive.
  • @lime-picker-icon-color, @lime-picker-icon-focused-color.
  • @lime-progressbutton-track-active-color, @lime-progressbutton-track-focused-color.
  • @lime-steps-indicator-icon-inactive-color, @lime-steps-step-icon-success-color, @lime-steps-step-label-active-color, @lime-steps-step-label-inactive-color.
  • @lime-switchitem-item-selected-container-color.
  • @lime-tablayout-tab-button-selected-color, @lime-tablayout-tab-container-selected-color, @lime-tablayout-tab-group-container-color.
  • @lime-virtuallist-container-color.
  • @sand-alert-overlay-focus-text-color, @sand-alert-overlay-disabled-selected-color, @sand-alert-overlay-disabled-selected-bg-color, @sand-alert-overlay-disabled-selected-focus-color, @sand-alert-overlay-disabled-selected-focus-bg-color, @sand-alert-overlay-progress-color-rgb, @sand-alert-overlay-progress-bg-color-rgb, @sand-alert-overlay-progress-bg-color-alpha, @sand-alert-overlay-checkbox-color, @sand-alert-overlay-checkbox-disabled-selected-text-color, @sand-alert-overlay-formcheckboxitem-focus-text-color, @sand-alert-overlay-item-disabled-focus-bg-color.
  • @sand-dropdown-title-color,
  • @sand-fixedpopuppanels-scrimtransparent-bg-color.
  • @sand-item-focus-background.
  • @sand-picker-joined-text-color, @sand-picker-joined-incrementer-color, @sand-picker-joined-focus-incrementer-color, @sand-picker-joined-focus-disabled-indicator-bg-color, @sand-picker-joined-focus-disabled-indicator-opacity, @sand-picker-joined-focus-disabled-indicator-active-opacity.
  • @sand-popuptablayout-shadow, @sand-popuptablayout-scrimtransparent-bg-color.
  • @sand-radioitem-disabled-selected-color, @sand-radioitem-disabled-selected-bg-color, @sand-radioitem-disabled-selected-border-color, @sand-radioitem-focus-disabled-selected-color, @sand-radioitem-focus-disabled-selected-bg-color, @sand-radioitem-focus-disabled-selected-border-color.
  • @sand-slider-active-bar-bg-color, @sand-slider-disabled-focus-knob-bg-color, @lime-slider-disabled-knob-bg-color, @sand-slider-focus-bar-bg-color, @sand-slider-focus-load-bg-color, @sand-slider-focus-knob-bg-color, @sand-slider-active-fill-bg-color.
  • @sand-spinner-text-shadow-color.
  • @sand-tablayout-tab-horizontal-border-color, @sand-tablayout-tab-horizontal-selected-border-color.
  • @sand-video-feedback-mini-text-color, @sand-video-player-title-text-shadow.

ReactDOM.findDOMNode API is removed. Placeholder.PlaceholderControllerDecorator and ViewManager is changed to have sibling DOM node by using @enact/core/internal/WithRef to access DOM nodes.

As ReactDOM.findDOMNode API is removed from ViewManager, transition needs index of the node to animate. Each panel must be wrapped with <div> instead of <> to map properly to the transition group.

spotlight/Spottable is changed to have sibling DOM node by using @enact/core/internal/WithRef to access DOM nodes instead of using ReactDOM.findDOMNode.