Skip to content
cccskills
FreePublished on GitHub

handsontable-css-dev

Guides changes to Handsontable themes and CSS, including cell spacing, text, icons, and theme tokens—named styling settings. It explains compatibility rules and the files and checks needed to keep theme changes consistent.

OriginalShow the original description

Use when working with Handsontable themes, CSS custom properties, SCSS files, theme tokens, or visual styling - covers theme architecture, CSS variable API, the strict CSS/JS separation rule, and the four-layer process for adding or renaming theme tokens

See how to install

Good for

  • Adjust cell spacing without disrupting scrolling
  • Add or rename theme tokens
  • Fix text styles affected by the host page
  • Update icons across themes

Files included (1)

  • SKILL.md10.9 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

Theme and CSS Development

Themes

Three themes, each with a -no-icons variant (6 bundles): ht-theme-main (default), ht-theme-classic, ht-theme-horizon, and ht-theme-main-no-icons, ht-theme-classic-no-icons, ht-theme-horizon-no-icons. Test all 3 themes after any styling change.

Rules

  • CSS custom properties are the public API for theming. Renaming or removing one is a breaking change: keep the old variable working.
  • Renaming or removing a CSS class is a breaking change: keep the old class in the DOM.
  • CSS and JS live in separate files.
  • Every CSS feature must work in the browser-targets.js browsers (eslint-plugin-compat enforces it).

Cell padding comes from the variables

StylesHandler#getDefaultRowHeight() computes --ht-line-height + 2 * --ht-cell-vertical-padding + border-bottom-width, and Walkontable sizes a table's scroll range from the summed row heights. A padding written onto a td leaves the variable at the theme value, so the scroll range comes out wrong. Override --ht-cell-vertical-padding / --ht-cell-horizontal-padding and derive the td padding from them.

A nested grid built inside a hidden container masks this: its styles cache is empty, the derived row height reads null, and the engine measures the DOM.

File structure and commands

PathContents
src/styles/Base SCSS
src/themes/Theme SCSS
handsontable/styles/Compiled CSS, gitignored (handsontable/.gitignore), never committed; regenerated by build:styles/build:themes-css

build:styles compiles SCSS, build:themes-* builds theme assets, npm run stylelint --prefix handsontable lints CSS/SCSS.

No :has() in stylesheets

The stylelint rule handsontable/no-has-selector (error) bans :has() in src/**/*.{css,scss}: in Chrome it re-runs style invalidation across the host page on every grid DOM mutation, and the grid mutates on every scroll render. Drive the style from a class that JS toggles on the target element (reference pattern: SelectionManager #markActiveHeaderNeighbors and the -seam taggers).

The rule lives in .config/plugin/stylelint/, a pnpm file: dependency that is copied, not symlinked: run pnpm install after editing it.

An exception for state not re-evaluated during a scroll (dialog focus, dropdown selection, offscreen .htGhostTable) uses // stylelint-disable-next-line handsontable/no-has-selector -- <reason> with the reason why it is off the scroll path.

Text-bearing spans declare their own text metrics

Every <span> (or other inline element) the grid renders with visible text declares font-size, line-height, font-weight, letter-spacing, and font-family itself. A host rule on the bare element (span { font-size: 20px }, specificity (0,0,1)) beats an inherited value (#11306, DEV-75). The older guards cover two properties only: span.colHeader/span.rowHeader (tokens in _base.scss), .htCheckboxRendererLabel, .ht-sheets-bar__tab-label; their font-weight, letter-spacing, and font-family still leak.

Pattern: the component root (.ht-pagination, td, .ht-sheets-bar) carries the token (font-size: var(--ht-font-size)), and every text-bearing span below it declares the five properties as inherit. That wins over the host's element selector and follows a user's override on the root, provided the override beats the root's rule on specificity. Source order does not decide it, because the core stylesheet is injected into <head> at init and often loads after the user's CSS. Core's cell rule .handsontable :where(...) > td is (0,1,1): a user's .handsontable td ties and loses on order, .handsontable tbody td (0,1,2) wins. The bar's .handsontable.ht-pagination is (0,2,0): div.handsontable.ht-pagination (0,2,1) wins. State three limits in the PR:

  • A user rule on the guarded class at lower specificity than the guard now loses: .ht-multi-select-chip { font-size: 16px } (guard (0,2,0)), .ht-page-navigation-section__label { font-weight: 600 }, and .handsontable .ht-page-navigation-section__label (guard .handsontable.ht-pagination ... (0,3,0)).
  • A bare .ht-pagination { font-size } never beat the bar's own .handsontable.ht-pagination rule.
  • Offsets computed from the --ht-line-height token (multiselect and autocomplete arrow top, chip end padding) do not follow a user's td line-height override.

Scope the declarations to the guarded classes. Do not add a blanket .handsontable span { ... } rule: its specificity (0,1,1) overrides a user's .my-class on spans inside custom renderers.

tests/e2e/host-span-styles.spec.ts (fixture tests/fixtures/demo/host-span-styles.html) hosts a hostile span {} rule and asserts the guarded spans against their cascade parent. Its user-override tests prepend their rule to <head> (HostSpanStylesPage.prependUserStyles). Add every new text-bearing span to that spec. The fixture loads the compiled handsontable/styles/*.min.css and a dist/ bundle that injects the core stylesheet again at init, so after an SCSS edit run the full npm --prefix handsontable run build before trusting a spec run; build:styles and build:styles.min alone leave the bundle's copy stale and a reverted guard still passes.

Icons

Every built-in icon is a real element: <i class="ht-icon ht-icon-<kebab-name>" aria-hidden="true"></i>, built by two functions in handsontable/src/themes/engine/icons.ts:

  • createIcon(hot, name, options) builds a fresh icon (options: { flipInRtl?, className? }).
  • syncIcon(hot, container, slotClass, name, options) keeps exactly one icon in a slot a hook re-renders: creates it when missing, keeps it when the name is unchanged, replaces it when the name changed, and removes it when name is null. Use it wherever a header/draw hook decides per render whether an icon shows; do not hand-roll createIcon plus manual remove/replace bookkeeping there.

The glyph comes from the per-theme --ht-icon-<name> variable plus a generated .ht-icon-<name> rule (mask-image: var(--ht-icon-<name>); background-color: currentColor). .ht-icon (handsontable/src/styles/components/core/_icon.scss) carries no paint, no background-color and no mask-image: a -no-icons bundle ships neither the variable nor the .ht-icon-<name> rule, so mask-image resolves to none and the icon renders as nothing. Paint on the base class would render every icon in a -no-icons bundle as a solid square. Add paint only to a generated .ht-icon-<name> rule.

  • Glyph vs. chrome. A component with two pseudo-elements has one drawing the glyph and one drawing a background, border, ring, or hover surface. Only the glyph one is replaced by the <i> element (its @include mixins.pseudo / content: '' dropped); the chrome one keeps its content unchanged. Read both &::before/&::after rules before touching either (::before is not always the glyph). Mixing them up shipped four times: radio focus ring, sheets-bar chevron hover, checkbox box, collapsible-column indicator background.
  • RTL mirroring. A mirrored icon carries ht-icon--flip-rtl ([dir="rtl"] .ht-icon--flip-rtl { transform: scaleX(-1); }). Its positioning rule must never also set transform (such as translateY(-50%)): the declarations collide and the mirror silently stops, visible only under [dir="rtl"]. Center it with a negative margin or a flex/grid container.
  • Migrating a site: move the state rules too. Re-key every :hover, :focus, :disabled, :checked, or .is-active rule that colored the old pseudo-element onto .ht-icon (or a class the <i> carries); otherwise the glyph stays visually dead on state changes.
  • Adding or renaming an icon touches four lists that a test or tsc cross-checks: the generator side in handsontable/scripts/themes/figma/.ai/CONVENTIONS.md ("Adding or changing an icon") and IconKey/ICON_NAMES/VALID_ICON_KEYS at runtime.

Adding a new theme token: four layers

For a --ht-<component>-<property> variable and its JS token. A rename or removal follows the same playbook plus a legacy-alias path. Group the new entry next to related ones in each file.

Layer 1: static CSS defaults (6 files)

handsontable/src/themes/static/css/theme/ht-theme-{main,classic,horizon}{,-no-icons}.css

Define --ht-<kebab-case-name> and its resolved default per theme, usually via var(...) of lower-level tokens so overrides keep cascading. All themes declare the same key set. Symptom when missing: the variable is undefined in DevTools while ThemeBuilder tests that skip rendered styles pass.

Layer 2: token JS runtime defaults (3 files)

handsontable/src/themes/static/variables/tokens/{main,classic,horizon}.ts

These drive ThemeBuilder. camelCase keys mirror the CSS variable (paginationButtonBorderColor for --ht-pagination-button-border-color); values use 'tokens.otherTokenName' strings or primitive arrays like ['colors.palette.100', 'colors.palette.700']. Symptom when missing: createTheme() ignores the token.

Layer 3: validation allow-list

handsontable/src/themes/engine/utils/validation.ts: add the key to the VALID_TOKEN_KEYS Set, in the matching semantic section (e.g. // Pagination). Symptom when missing: ThemeBuilder logs [ThemeBuilder] Unknown token key: "xxx", and src/themes/engine/__tests__/builder.unit.js fails "should not warn for unknown token keys when using built-in tokens in createTheme" (it iterates every mainTokens key to catch layer 2/3 drift).

Layer 4: TypeScript types

handsontable/src/themes/types.ts: add the key to the TokenKey union. Edit the source; tmp/themes/types.d.ts is generated by build:types. Symptom when missing: npm run test:types --prefix handsontable (tsc -p ./test/types) fails with a TokenKey assignability error.

Layer 5 (soft): docs

docs/content/guides/styling/theme-customization/theme-customization.md: add the token to the variables reference table (CSS name + JS name, then description).

Naming

JS camelCase maps to CSS kebab-case with the --ht- prefix by convention (no generator): dialogContentPaddingHorizontal is --ht-dialog-content-padding-horizontal.

SCSS consumption

Reference the variable from the component SCSS under src/styles/components/ (e.g. _pagination.scss) as var(--ht-<name>). Use the var(--ht-<name>, <fallback>) form only for a legacy compatibility fallback, because a fallback masks missing-variable bugs.

Pre-flight

  1. CSS in all 6 theme files.
  2. JS token in all 3 runtime files.
  3. Key in the validation.ts allow-list.
  4. Key in TokenKey.
  5. npm run test:unit --prefix handsontable --testPathPattern=themes passes.
  6. npm run test:types --prefix handsontable passes.
  7. Docs table updated.

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Develop and modify the Handsontable Angular wrapper with guidance on component lifecycle, settings updates, and NgZone, which controls change detection. It also covers standalone examples, global configuration, and the build and test workflow.

  • Fix settings updates in the Angular wrapper
  • Review change detection around grid operations
  • Update examples to standalone components
handsontable/handsontable22.1KUpdated Oct 19, 2026

Open local or public web pages in BrowserStack Live for checks on real mobile devices or desktop browsers. The skill prepares temporary access to a local server, launches the requested session, and shares its dashboard link.

  • Check a local page on an Android device
  • Open a page in Safari on an iPhone or iPad
  • Inspect behavior in a specific desktop browser
handsontable/handsontable22.1KUpdated Oct 19, 2026

Prepare user-facing changelog entries for source code changes. The skill determines whether an entry is needed, classifies the change, writes a clear title, and uses the project CLI to create a JSON file tied to the pull request.

  • Add a changelog entry for a bug fix
  • Classify a new feature or behavior change
  • Explain a breaking change and its replacement
handsontable/handsontable22.1KUpdated Oct 19, 2026

code-graph

FreeSummary

Explore relationships between functions and files using a prebuilt code graph, a map of code dependencies. Trace bugs, inspect callers and execution paths, and assess the impact of refactoring or proposed changes.

  • Explore callers and dependencies in unfamiliar code
  • Trace a bug across multiple files
  • Assess the impact of renaming or removing code
handsontable/handsontable22.1KUpdated Oct 19, 2026

Work with Handsontable row and column positions by distinguishing source data, displayed order, and rendered cells. It guides index conversion and helps investigate misplaced data when sorting, filtering, hiding, or moving rows and columns.

  • Investigating misplaced rows or columns
  • Converting between source and displayed indexes
  • Combining filters with manual column movement
handsontable/handsontable22.1KUpdated Oct 19, 2026

Create Handsontable documentation examples for JavaScript, TypeScript, React, Angular, and Vue. The skill explains source files, generated JavaScript, required imports and license settings, embedding examples in guides, and checking that each framework version displays correctly.

  • Adding a code example to a feature guide
  • Preparing examples for multiple frameworks
  • Generating JavaScript examples from TypeScript
handsontable/handsontable22.1KUpdated Oct 19, 2026

See all skills by handsontable

Report a problem with this skill