React Data GridMigrate from 16.2 to 17.0

Migrate from Handsontable 16.2 to Handsontable 17.0, released on March 09, 2025.

More information about this release can be found in the 17.0.0 release blog post (opens new window).
For a detailed list of changes in this release, see the Changelog.

1. Legacy styles have been removed

Starting from version 17.0.0, the legacy stylesheet (dist/handsontable.full.min.css) has been completely removed from Handsontable. If you're upgrading from an earlier version and still using the legacy styles, you must migrate to a theme.

Using the main theme without modifications

If you want to use the main theme without any modifications, you don't need to configure anything. Handsontable will automatically use the main theme with default settings. However, if you want to retain the legacy look and feel, migrate to the Classic theme as described below.

What Changed

  • The legacy CSS file (dist/handsontable.full.min.css) is no longer available
  • You must now use the new theming system with either the Theme API or CSS-based themes
  • The Classic theme (ht-theme-classic) provides the same visual appearance as the legacy styles

How to Migrate

If you were using the legacy styles, migrate to the Classic theme using one of the two options below.

The Theme API allows you to register and configure themes programmatically with runtime features like density modes and color schemes.

Step 1: Update your CSS imports

- import 'handsontable/dist/handsontable.full.min.css';

Step 2: Import and register the Classic theme

import { HotTable } from '@handsontable/react-wrapper';
import { classicTheme } from 'handsontable/themes';

function App() {
  return (
    <HotTable
      theme={classicTheme}
      // ... other options
    />
  );
}

Option 2: Using CSS files with theme as string

Alternatively, you can use CSS files and pass the theme name as a string to the theme option.

Step 1: Update your CSS imports

- @import 'handsontable/dist/handsontable.full.min.css';
+ @import 'handsontable/styles/ht-theme-classic.min.css';

Or if you're using JavaScript imports:

- import 'handsontable/dist/handsontable.full.min.css';
+ import 'handsontable/styles/ht-theme-classic.min.css';

Step 2: Set the theme in Handsontable configuration

<HotTable
  theme="ht-theme-classic"
  // ... other options
/>

Why Migrate to Classic?

The Classic theme provides the same visual appearance as the legacy style, but with significant improvements:

  • CSS Variables: Easily customize colors, spacing, and other visual properties
  • Dark Mode Support: Built-in dark mode variants (ht-theme-classic-dark and ht-theme-classic-dark-auto)
  • Better Maintainability: The theming system is designed for long-term support
  • Consistency: Works seamlessly with the new Design System

2. Migrate from CSS-based themes to the Theme API

If you're currently using CSS-based themes (loading theme CSS files and passing theme name as a string), we recommend migrating to the Theme API for better runtime control and customization options.

What Changed

The Theme API provides a programmatic way to configure themes with runtime features that CSS-based themes don't support:

  • Runtime color scheme switching: Change between light, dark, and auto modes programmatically
  • Density types: Switch between compact, default, and comfortable density modes
  • Dynamic customization: Modify theme parameters at runtime using the params() method

How to Migrate

Before: CSS-based theme

import 'handsontable/styles/handsontable.min.css';
import 'handsontable/styles/ht-theme-main.min.css';

function App() {
  return (
    <HotTable
      theme="ht-theme-main"
      // ... other options
    />
  );
}

After: Theme API

import { HotTable } from '@handsontable/react-wrapper';
import { mainTheme, registerTheme } from 'handsontable/themes';

const theme = registerTheme(mainTheme)
  .setColorScheme('auto')
  .setDensityType('default');

function App() {
  return (
    <HotTable
      theme={theme}
      // ... other options
    />
  );
}

Advanced: Customizing theme parameters

The Theme API allows you to customize theme parameters dynamically using the params() method:

import { mainTheme, registerTheme } from 'handsontable/themes';

const theme = registerTheme(mainTheme);

// Customize theme parameters
theme.params({
  colors: {
    primary: {
      500: '#9333ea', // Custom primary color
    },
  },
  tokens: {
    fontSize: '14px',
    borderColor: ['colors.palette.200', 'colors.palette.700'], // [light, dark]
  },
});

Available themes

The following themes are available through the Theme API:

Theme Import Description
Main mainTheme Spreadsheet-like interface, perfect for batch-editing tasks
Horizon horizonTheme Cleaner look without vertical lines, ideal for data display
Classic classicTheme Retains the legacy style appearance with CSS variable support

3. Migrate from Numbro Format to Intl.NumberFormat

Handsontable 17.0 introduces native support for the Intl.NumberFormat (opens new window) API for numeric formatting. The numbro.js-based formatting (pattern and culture options) will be removed in next major release.

What Changed

  • Native Intl Support: The numericFormat option now accepts all properties of Intl.NumberFormatOptions
  • Locale Separation: Locale is now controlled via the locale cell property instead of numericFormat.culture
  • Standard API: Uses the browser's native internationalization API, which is more performant and widely supported
  • Removed Dependency: In the next major release, the numbro.js library will be removed from Handsontable

Why This Change

The numbro.js library added unnecessary bundle size and maintenance overhead. The native Intl.NumberFormat API provides the same functionality with better performance, broader browser support, and no external dependencies. This change aligns Handsontable with web standards and reduces the overall package size.

How to Migrate

Step 1: Update numericFormat Configuration

Replace pattern and culture properties with Intl.NumberFormat options.

Before:

<HotTable
  columns={[{
    type: 'numeric',
    numericFormat: {
      pattern: '0,0.00 $',
      culture: 'en-US'
    }
  }]}
/>

After:

<HotTable
  columns={[{
    type: 'numeric',
    locale: 'en-US',
    numericFormat: {
      style: 'currency',
      currency: 'USD',
      minimumFractionDigits: 2
    }
  }]}
/>

Step 2: Common Migration Patterns

Currency Formatting:

// Before
numericFormat: {
  pattern: '0,0.00 $',
  culture: 'en-US'
}

// After
locale: 'en-US',
numericFormat: {
  style: 'currency',
  currency: 'USD',
  minimumFractionDigits: 2
}

Decimal Formatting:

// Before
numericFormat: {
  pattern: '0,0.00',
  culture: 'en-US'
}

// After
locale: 'en-US',
numericFormat: {
  style: 'decimal',
  useGrouping: true,
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
}

Percent Formatting:

// Before
numericFormat: {
  pattern: '0.00%',
  culture: 'en-US'
}

// After
locale: 'en-US',
numericFormat: {
  style: 'percent',
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
}

Step 3: Using Numbro After Migration

If you need numbro.js-specific formatting features that aren't available in Intl.NumberFormat, you can create a custom cell type using the numbro library. See the Numbro cell type recipe for a complete implementation guide.

What to Expect

  • Console Warning: You'll see a deprecation warning if you're still using pattern or culture options
  • Same Functionality: Most common formatting patterns can be replicated using Intl.NumberFormat options
  • Better Performance: Native API is faster and doesn't require external dependencies

Timeline

  • Version 17.0: Numbro format deprecated with warnings
  • Version 18.0: Numbro format options (including dependencies) will be removed

4. Migrate from Moment.js Format to Intl.DateTimeFormat

Handsontable 17.0 introduces native support for the Intl.DateTimeFormat (opens new window) API for date and time formatting. The Moment.js-style string formats (dateFormat and timeFormat as strings) used by the legacy date and time cell types will be removed in the next major release.

What Changed

  • Native Intl Support: The dateFormat and timeFormat options now accept all properties of Intl.DateTimeFormat options when using intl-date and intl-time cell types
  • Locale Separation: Locale is controlled via the locale cell property instead of being implied by the format string
  • New Cell Types: Use intl-date for dates and intl-time for times instead of date and time when migrating
  • Source Data: For intl-date, store values in ISO 8601 date format (YYYY-MM-DD). For intl-time, store values in 24-hour format (HH:mm, HH:mm:ss, or HH:mm:ss.SSS)
  • Deprecated options: The correctFormat option (auto-correction of entered date/time format for legacy date/time cells) and the datePickerConfig option (Pikaday-based date picker for the legacy date cell type) are deprecated and will be removed in the next major release

Why This Change

Moment.js is in maintenance mode and the legacy date/time cell types depend on it for string-format parsing. The native Intl.DateTimeFormat API provides locale-aware formatting without external dependencies, with better performance and alignment with web standards. This change reduces bundle size and keeps date/time behavior consistent with the rest of the platform.

How to Migrate

Step 1: Update Date Columns

Replace the date cell type and string dateFormat with intl-date and an Intl.DateTimeFormat options object.

Before:

<HotTable
  columns={[{
    type: 'date',
    dateFormat: 'YYYY-MM-DD'
  }]}
/>

After:

<HotTable
  columns={[{
    type: 'intl-date',
    locale: 'en-US',
    dateFormat: {
      year: 'numeric',
      month: '2-digit',
      day: '2-digit'
    }
  }]}
/>

Step 2: Update Time Columns

Replace the time cell type and string timeFormat with intl-time and an Intl.DateTimeFormat options object.

Before:

<HotTable
  columns={[{
    type: 'time',
    timeFormat: 'h:mm:ss a'
  }]}
/>

After:

<HotTable
  columns={[{
    type: 'intl-time',
    locale: 'en-US',
    timeFormat: {
      hour: 'numeric',
      minute: '2-digit',
      second: '2-digit',
      hour12: true
    }
  }]}
/>

Step 3: Common Migration Patterns

Short date (e.g. DD/MM/YYYY → locale short):

// Before
type: 'date',
dateFormat: 'DD/MM/YYYY'

// After
type: 'intl-date',
locale: 'en-GB',
dateFormat: { dateStyle: 'short' }

ISO date (YYYY-MM-DD):

// Before
type: 'date',
dateFormat: 'YYYY-MM-DD'

// After
type: 'intl-date',
locale: 'en-US',
dateFormat: {
  year: 'numeric',
  month: '2-digit',
  day: '2-digit'
}

12-hour time with seconds:

// Before
type: 'time',
timeFormat: 'h:mm:ss a'

// After
type: 'intl-time',
locale: 'en-US',
timeFormat: {
  hour: 'numeric',
  minute: '2-digit',
  second: '2-digit',
  hour12: true
}

Time style shortcut:

// After
type: 'intl-time',
locale: 'en-US',
timeFormat: { timeStyle: 'medium' }

Step 4: Custom Cell Types Using Moment.js

If you use custom cell types that rely on Moment.js for formatting or parsing (e.g. recipes like the Moment.js date or time cell type), replace Moment formatting with Intl.DateTimeFormat in your renderer and editor logic. For a full custom implementation that still uses Moment, see the Moment.js date and Moment.js time recipes; consider migrating those implementations to Intl to avoid the deprecated path.

What to Expect

  • Console Warning: You'll see a deprecation warning if you still use string dateFormat or timeFormat with the date or time cell types, or if you use the correctFormat or datePickerConfig options
  • Data Format: Ensure date values are in YYYY-MM-DD and time values in 24-hour form; intl-date/intl-time only change display, not storage
  • Same Look: Use dateStyle/timeStyle or component options to approximate previous Moment format output

Timeline

  • Version 17.0: String-based dateFormat and timeFormat, and the correctFormat and datePickerConfig options, are deprecated and emit console warnings; the intl-date and intl-time cell types are available.
  • Version 18.0: The current intl-date and intl-time cell types will become the default date and time cell types; intl-date and intl-time will remain as aliases. The Moment.js library will be removed from dependencies. Options correctFormat and datePickerConfig will be removed.

5. Migrate from built-in DOMPurify to the sanitizer option

Handsontable 17.0 introduces a configurable sanitizer option for HTML content. The built-in use of the DOMPurify (opens new window) library is deprecated and will be removed in the next major release. After that, if you do not set a custom sanitizer, any string containing HTML will be stripped before rendering (no rich HTML). To keep sanitized HTML (e.g. with DOMPurify or another library), set the sanitizer option to your own sanitizer function.

What Changed

  • New sanitizer option (v17.0): A table-level option that accepts a function (content, source) => string. It is used whenever HTML is written to the DOM (cell values, headers, context menu labels, dialog markup, clipboard paste).
  • Default behavior: In 17.0 the grid still uses DOMPurify by default but shows a deprecation warning. In the next major release, DOMPurify is removed; with no custom sanitizer, HTML in content will be stripped.
  • Source parameter: The second argument indicates where the content is used ('innerHTML' or 'CopyPaste.paste'), so you can apply different rules per context.

Why This Change

Making sanitization configurable gives you control over allowlists, library choice, and integration with Trusted Types and CSP. It also allows removing DOMPurify from the bundle for applications that use a different sanitization strategy or none.

How to Migrate

Install DOMPurify in your project and pass a function that calls it via the sanitizer option:

import DOMPurify from 'dompurify';
import { HotTable } from '@handsontable/react-wrapper';

function App() {
  return (
    <HotTable
      sanitizer={(content, source) => {
        if (source === 'CopyPaste.paste') {
          return DOMPurify.sanitize(content, {
            ADD_TAGS: ['meta'],
            ADD_ATTR: ['content'],
            FORCE_BODY: true,
          });
        }

        return DOMPurify.sanitize(content);
      }}
      // ... other options
    />
  );
}

Option 2: Strip all HTML (plain text only)

If you do not want rich HTML, use a sanitizer that escapes or strips tags:

sanitizer: (content, source) => {
  const tpl = document.createElement('template');

  tpl.innerHTML = content;

  const text = tpl.content.textContent ?? '';

  return text
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

Option 3: Trusted Types (CSP)

If you enforce Trusted Types, wrap your sanitizer in a policy and return its createHTML result. Add the policy name to your CSP trusted-types directive (e.g. trusted-types default handsontable):

const policy = window.trustedTypes?.createPolicy('handsontable', {
  createHTML: (input) => DOMPurify.sanitize(input),
});

sanitizer: (content, source) =>
  policy ? policy.createHTML(content) : DOMPurify.sanitize(content),

What to Expect

  • Console warning in 17.0: If you keep the default (DOMPurify) without setting sanitizer, a deprecation warning is shown pointing to the sanitizer option.
  • Next major release: DOMPurify is removed from dependencies. Without a custom sanitizer, HTML in cell content, headers, menus, and paste will be stripped before rendering.
  • No per-column sanitizer: The option is table-level only; it does not work when set per column or per cell.

Timeline

  • Version 17.0: sanitizer option is available; default remains DOMPurify with a deprecation warning.
  • Next major release: DOMPurify is removed. If no sanitizer is set, HTML content is stripped.

6. core-js dependency removed

Starting in version 17.0, Handsontable no longer depends on or bundles core-js (opens new window). The library relied on it in the past for polyfills (e.g. ECMAScript 5/6 features, Promises, Symbols, collections). That dependency has been removed to reduce bundle size and to avoid forcing a specific polyfill set on applications that target modern environments only.

What Changed

  • No core-js in the package: The core-js package is no longer a dependency of Handsontable
  • No bundled polyfills: The built bundles no longer include core-js modules
  • Modern runtimes: Handsontable 17.0 targets environments that already provide the required APIs (see browser support)

Why This Change

core-js added significant size to the bundle and was unnecessary for applications that only support modern browsers and Node versions. Dropping it lets the library stay smaller and lets each application choose its own polyfill strategy (or none) based on its own target environment.

How to Migrate

If your application or build previously relied on Handsontable (or its build tooling) to pull in core-js, and you still need to support older environments that lack certain APIs:

  1. Add polyfills in your app: Install and import core-js (or another polyfill library) in your own project and load it before Handsontable, e.g. in your entry file or polyfills bundle.
  2. Use your bundler's polyfill options: If you use a bundler (e.g. Webpack, Vite) that can inject polyfills based on browserslist, configure it for your target browsers so that only the required polyfills are included.

If you only target modern browsers and runtimes, no action is needed.

What to Expect

  • Smaller Handsontable bundle: The library no longer ships polyfill code
  • No automatic polyfills: You must add and maintain polyfills yourself if you support older environments
  • Same API: Handontable's public API is unchanged; only the dependency set has changed

Timeline

  • Version 17.0: core-js is removed from Handsontable dependencies and build output

7. Built-in HyperFormula (deprecation notice)

The Formulas plugin uses HyperFormula (opens new window) as its calculation engine. Currently, HyperFormula is bundled with Handsontable. In version 18.0, it will be removed from package.json.

What to Expect

  • Version 17.x: Built-in HyperFormula remains available, no change required yet.
  • Version 18.0: HyperFormula is removed from Handsontable dependencies. You must add it as your own dependency and configure the Formulas plugin to use your instance.

How to Prepare

  1. Install HyperFormula in your project (e.g. npm install hyperformula).
  2. Import HyperFormula and pass it to the Formulas plugin with licenseKey: 'internal-use-in-handsontable'.

See the Formula calculation guide for configuration details.

Summary of breaking changes

Change Action Required
Legacy stylesheet removed Migrate to Classic theme or another built-in theme
handsontable.full.min.css no longer available Use Theme API or import a theme CSS file (e.g. ht-theme-classic.min.css)
CSS-based themes (optional migration) Consider migrating to Theme API for runtime features
core-js dependency removed Add core-js or other polyfills in your app if you support older environments
Built-in HyperFormula (deprecation) In 18.0, import HyperFormula yourself and pass it to the Formulas plugin with licenseKey: 'internal-use-in-handsontable'