Skip to content

feat: add autoHeaderHeight option for multi-line column headers - #1282

Merged
ghiscoding merged 6 commits into
6pac:masterfrom
jahanbakhsh18:feature/auto-header-height
Sep 1, 2026
Merged

feat: add autoHeaderHeight option for multi-line column headers#1282
ghiscoding merged 6 commits into
6pac:masterfrom
jahanbakhsh18:feature/auto-header-height

Conversation

@jahanbakhsh18

@jahanbakhsh18 jahanbakhsh18 commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Add autoHeaderHeight option for multi-line column headers

This PR adds a new autoHeaderHeight grid option that allows column headers to auto-size based on their content.

Closes #1272

Features

  • Disabled by default to preserve existing behavior.
  • Recalculate the header height:
    • after column headers are created
    • after a column resize is completed
    • when autoHeaderHeight is enabled or disabled through setOptions
  • Supports frozen columns by keeping left and right header panes aligned.
  • Adds a dedicated example and Cypress test coverage.

The grid core handles measuring and synchronizing the required header height. The calculated dimensions are passed to the themes through CSS custom properties, allowing to retain their respective header layout behavior.

Demo

A short demo video and screenshots are attached below.

example-auto-header-height.mp4
example-auto-header-height

Comment thread src/styles/slick-alpine-theme.scss Outdated
Comment thread src/styles/slick-alpine-theme.scss Outdated
Comment thread src/slick.grid.ts Outdated
Comment thread src/slick.grid.ts Outdated
@ghiscoding

Copy link
Copy Markdown
Collaborator

@jahanbakhsh18 feel free to click on the the "Resolve Comment" button when you've addressed any of the comments I left.

also CCing @muendlein as well so that he's aware of the PR and can also provide feedback

@muendlein

Copy link
Copy Markdown
Contributor

From my side I would also like to see an example where a column header is multiline HTML string or a DOM element.
Preferably also in a scenario with fit column autosizing.
In this context I see the use case for some finer control on per column level. One example use case is that a column with a multiline HTM string should not be cut off while other plain string column headers should not be wrapped. This is especially relevant in case a column header has many short words which can lead to excessive header height when wrapped.

@jahanbakhsh18

jahanbakhsh18 commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @muendlein for the suggestion. I created an additional example to check the feasibility of these scenarios with the current autoHeaderHeight implementation.
I tested the following header types:

  • A multi-line HTML string
  • Another multi-line HTML string with different content styling
  • A DOM element used directly as the column name
  • A regular long text header
...
const headerElement = document.createElement('p');
headerElement.style.margin= 0;
headerElement.innerHTML = `Customer <span style="color:red;">Information</span>`;

var columns = [
  { id: "sel", name: "#", field: "num", behavior: "select", cssClass: "cell-selection", 
    width: 30, cannotTriggerInsert: true, resizable: false, unselectable: true 
  },
  { id: "html-header", name: "Customer<br><strong style=\"color:red;\">Information</strong>", 
    field: "title", width: 100 
  },
  { id: "html-header-2", name: "Project<br><span style='font-size: 8px'>Status & Details</span>", 
    field: "title2", width: 100 
  },
  { id: "header-element", name: headerElement, field: "title3", width: 100 },
  { id: "description", name: "Very Long Header With Several Words", field: "title4", width: 150 }
];

var options = { 
  enableCellNavigation: true, enableColumnReorder: false, autoHeaderHeight: true, frozenColumn: 1 
};
...
html-element-long

Note:

The current implementation handles all of these cases without any additional changes. The header height is calculated from the rendered content, so the multi-line HTML and DOM element are included naturally in the measurement.
I also agree that finer per-column control could be useful, especially for cases where only selected columns should wrap while other columns should remain on a single line. However, that would introduce a separate per-column wrapping/layout API and is beyond the scope of the current PR, whose goal is to provide the basic infrastructure for automatic header height calculation and synchronization.

Comment thread src/slick.grid.ts Outdated
@muendlein

Copy link
Copy Markdown
Contributor

@jahanbakhsh18 Thank you, this is looking good!
For the example itself what happens when a autosizing mode like IgnoreViewport is active? Especially in combination with using the ignoreHeaderText option.

@ghiscoding

Copy link
Copy Markdown
Collaborator

Hey everyone, I asked Codex (ChatGPT 5.6) to review PR #1282, all PR comments, and the original issue #1272. This is only a second opinion for discussion, not a maintainer verdict.

Context from issue #1272

The original issue is specifically about preventing rendered content inside .slick-header-column from being silently clipped. This includes:

  • Multi-line HTML header titles
  • DOM elements used as header titles
  • Headers requiring more than two lines
  • Frozen column and frozen row layouts
  • Recalculation when column widths or the grid container size changes

The original issue prototype also identified a frozen-layout problem where the two header panes had different heights and the grid overflowed its container.

Review findings

1. High — header columns can still clip rendered content

The PR changes .slick-column-name to overflow: visible, but .slick-header-column itself still has overflow: hidden in both the base styles and Alpine theme.

Therefore, multi-line HTML or DOM content can still be clipped by its parent header column.

Relevant rules:

The auto-height styles should also override the parent column:

.slick-header-column {
  height: auto;
  overflow: visible;
}

The current measurement also removes --slick-auto-header-height, leaving declarations such as:

height: calc(var(--slick-auto-header-height, auto) - 8px);

calc(auto - 8px) is invalid CSS. A safer pattern is:

.slick-header-column {
  height: auto;
  height: calc(var(--slick-auto-header-height) - 8px);
  overflow: visible;
}

For Alpine:

.slick-header-column {
  height: auto;
  height: calc(
    var(--slick-auto-header-height) -
    var(--alpine-auto-header-height-extra, v.$alpine-auto-header-height-extra)
  );
  overflow: visible;
}

The first height: auto declaration is used while the CSS variable is absent. Once JavaScript sets the variable, the calculated height takes effect.

The current clipping test does not fully verify this because it checks the first header column, which is the # column and is not a multi-line header.

2. Medium — programmatic autosizing does not remeasure header height

reRenderColumns() applies new header widths but does not call recalculateHeaderHeight().

This matters for IgnoreViewport and ignoreHeaderText, because those settings can make columns narrower and cause their headers to wrap differently.

Suggested change:

reRenderColumns(reRender?: boolean) {
  this.applyColumnHeaderWidths();
  this.updateCanvasWidth(true);

  if (this._options.autoHeaderHeight) {
    this.recalculateHeaderHeight();
  }

  this.trigger(this.onAutosizeColumns, { columns: this.columns });

  if (reRender) {
    this.invalidateAllRows();
    this.render();
  }
}

The existing manual column resize path already recalculates at resize end, which is preferable to recalculating on every mouse-move.

3. Medium — container resizing needs to trigger recalculation

The current container resize handler only calls:

this.resizeCanvas.bind(this)

If the grid width changes responsively, header wrapping can change while the stored header height remains stale.

A minimal change would be:

this._bindingEventService.bind(this._container, 'resize', () => {
  if (this._options.autoHeaderHeight) {
    this.recalculateHeaderHeight();
  } else {
    this.resizeCanvas();
  }
});

If the application relies on ordinary CSS/container resizing, a ResizeObserver may be more reliable than a native resize event on the container. Any observer should include a width-change guard to avoid repeated layout loops.

4. Medium — frozen rows need explicit regression coverage

The original issue identified problems with both frozen columns and frozen rows:

  • Different left/right header heights
  • Grid overflow beyond its container
  • Incorrect pane height calculations

The PR currently tests frozen columns, but not a frozen-row configuration.

At minimum, add a test using:

grid.setOptions({
  autoHeaderHeight: true,
  frozenColumn: 2,
  frozenRow: 5
});

The test should verify:

cy.get('.slick-header-left .slick-header-columns').then(($left) => {
  cy.get('.slick-header-right .slick-header-columns').should(($right) => {
    expect(Math.abs(
      $left[0].offsetHeight - $right[0].offsetHeight
    )).to.be.lessThan(2);
  });
});

cy.get('#myGrid').should(($grid) => {
  expect($grid[0].scrollHeight)
    .to.be.lte($grid[0].clientHeight + 1);
});

If this test fails, the fix belongs in resizeCanvas(). The enlarged column-header height must be included exactly once in the frozen top-pane/container calculation and must not be added again to the viewport height.

Because header height and available width can affect scrollbar presence, the recalculation should also use an only-if-changed guard or a bounded second pass.

5. Medium — the requested HTML/DOM scenarios are not committed as tests

The original issue explicitly mentions HTML strings and DOM elements. The PR discussion includes an additional example in a comment, but the committed example and Cypress spec do not currently test those cases.

Suggested test setup:

const headerElement = document.createElement('p');
headerElement.style.margin = '0';
headerElement.innerHTML = 'Customer <strong>Information</strong>';

const columns = [
  {
    id: 'html',
    name: 'Customer<br><strong>Information</strong>',
    field: 'title',
    width: 100
  },
  {
    id: 'dom',
    name: headerElement,
    field: 'title',
    width: 100
  }
];

The Cypress test should inspect every rendered header, not only the first column:

cy.get('.slick-header-column').each(($header) => {
  const header = $header[0] as HTMLElement;
  expect(header.scrollHeight).to.be.lte(header.clientHeight);
});

6. Minor — trailing whitespace

There is trailing whitespace at:

[slick.grid.scss](

)

Verification

  • Local checkout matches PR head e6548939.
  • Local npm run lint passes.
  • Local npm run build:prod passes.
  • GitHub CI passes, including the full Cypress suite.
  • All five inline review threads are resolved and outdated.
  • All submitted reviews are COMMENTED; there is currently no approval.
  • The latest top-level question from muendlein about IgnoreViewport and ignoreHeaderText remains unanswered.

Overall recommendation

The PR has the right general direction and correctly keeps the feature opt-in. However, before merging, I recommend fixing the parent-column overflow and CSS fallback, adding recalculation after programmatic and responsive width changes, and adding regression coverage for HTML/DOM titles and frozen rows.

The current tests demonstrate the fixed example scenario, but they do not yet fully prove the requirements described in issue #1272.

@jahanbakhsh18

Copy link
Copy Markdown
Contributor Author

@ghiscoding @muendlein Thank you both for the detailed feedback, and apologies for the delayed response...
(I was away for a few days).

I’ve spent some time going through the interactions between autoHeaderHeight, the different column autosizing modes, frozen columns/rows, and the header sizing/recalculation flow. There are a few more interactions here than I initially expected, so I wanted to understand the behavior properly rather than make isolated changes based only on the individual review comments.

Regarding IgnoreViewport / ignoreHeaderText, I’m now adding explicit coverage for those scenarios. I also found that it makes more sense to separate the example into two focused examples:

  • one focused on the auto-header-height mechanism itself, including enable/disable, frozen columns/rows, and keeping the header panes aligned
  • another focused on different header/content types and column autosizing, including HTML strings, DOM elements, regular text, IgnoreViewport, ignoreHeaderText, and related autosizing behavior.

This should make the examples and regression tests much easier to understand and should also directly cover the scenario you mentioned, @muendlein.

I’ll continue with the remaining review points once these interactions are settled. Thanks again for the feedback and for your patience.

@jahanbakhsh18

Copy link
Copy Markdown
Contributor Author

@6pac, @ghiscoding, @muendlein please accept my apologies for the delay on this PR.

While testing Review 2: “Programmatic autosizing does not remeasure header height”, I noticed that simply adding recalculateHeaderHeight() to reRenderColumns() causes a recursive loop and stack overflow with the LegacyForceFit autosize mode.
Because of that, I decided to test all six autosize modes more thoroughly and include them in the second example. This helped me identify and address a few related edge cases, particularly around container resizing and header height recalculation.

I’m working through the remaining reviews now and will do my best to finalize and cover all six reviews ASAP. Thank you for your patience!

@ghiscoding

Copy link
Copy Markdown
Collaborator

oops my latest PRs caused a single line conflict, shouldn't be too hard to fix though which seems to be to keep both lines.

jahanbakhsh18 and others added 4 commits September 1, 2026 13:06
- Add autoHeaderHeight boolean option (default: false)
- Support multi-line header text
- Equalize left/right header heights for frozen columns
- Auto-recalculate on column resize
- Add example and Cypress tests
- Remove center alignment (use theme default)
- Add SASS variables for Alpine theme
- Simplify _setAutoHeaderHeightStyles by removing constants
- Call recalculateHeaderHeight on init and column update
Co-authored-by: Ghislain B. <gbeaulac@gmail.com>
- prevent header content clipping in Classic and Alpine themes
- recalculate header height after programmatic column autosizing
- add autosize mode example covering all supported algorithms
- expand Cypress coverage for header content, frozen panes, and autosizing
- prevent recursive rendering with LegacyForceFit
@jahanbakhsh18
jahanbakhsh18 force-pushed the feature/auto-header-height branch from e654893 to 3852589 Compare September 1, 2026 10:07
@jahanbakhsh18

jahanbakhsh18 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Hi everyone,
I’ve now addressed the review feedback and completed the implementation for the auto header height changes.

What was updated

  • Fixed header content clipping for both Classic and Alpine themes.
  • Ensured .slick-header-column allows the automatically calculated header height and rendered HTML/DOM content to be visible.
  • Added the necessary CSS fallback for the auto header height variable.
  • Added recalculateHeaderHeight() to the reRenderColumns() pipeline so programmatic column autosizing correctly updates the header height.
  • Verified the behavior with autosizing modes
  • Investigated the LegacyForceFit resize pipeline and avoided introducing the recursive loop that could cause a stack overflow.
  • Kept the existing container resize pipeline unchanged for the other autosize modes. Their recalculation can be triggered through Apply, while LegacyForceFit continues to react through the existing resize pipeline.
  • Added regression coverage for:
    • Multi-line HTML headers
    • DOM element headers
    • Frozen columns and frozen rows
    • Header height toggling
    • Column resizing
    • ignoreHeaderText
    • Container resizing
    • Recursive rendering / stack-overflow scenarios

I also added a second example specifically for demonstrating the interaction between Auto Header Height and the different column autosizing modes.

Demo: Auto Header Height and Autosize Modes

example-auto-header-height-autosize.mp4

Thanks again for the reviews and guidance. I believe the current implementation and test coverage now provide a good regression suite for the original issue.

@ghiscoding

ghiscoding commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

@jahanbakhsh18 I've asked Codex for another review and it still identified some small change requests, could you please look into this? If you need help, I can always ask Codex to fix it, let me know if you need help or not. It would be great to have this released this week. Thanks

Here's the Codex review with code suggestions separated in 2 comments:

Thanks for the work on this — the feature direction looks good. I think we need one implementation update and stronger regression coverage before merging.

Programmatic autosizing leaves auto header height stale

reRenderColumns() updates the header widths, but it does not remeasure autoHeaderHeight.

This matters for the IgnoreViewport + ignoreHeaderText case mentioned above: autosizing can make a column narrower than its header text requires, causing the header to wrap after its height has already been fixed. The old CSS variable/header height then remains in place, so the newly wrapped title can be clipped.

Suggested change:

reRenderColumns(reRender?: boolean) {
  this.applyColumnHeaderWidths();
  this.updateCanvasWidth(true);

  if (this._options.autoHeaderHeight) {
    this.recalculateHeaderHeight();
  }

  this.trigger(this.onAutosizeColumns, { columns: this.columns });

  if (reRender) {
    this.invalidateAllRows();
    this.render();
  }
}

@jahanbakhsh18

Copy link
Copy Markdown
Contributor Author

Thanks @ghiscoding!

Could you please check the latest commit of the PR once more? I believe some of these comments may be based on an earlier version.

The latest version already includes:

  • The recalculateHeaderHeight() call in reRenderColumns().
  • The new example-auto-header-height-autosize.html example covering the autosizing modes, including IgnoreViewport and ignoreHeaderText.
  • The HTML and DOM header scenarios in the new example.
  • The corresponding Cypress coverage, including the 18 tests in example-auto-header-height-autosize.cy.ts.

I’ll be happy to add anything that is still missing after reviewing the latest version 🙂

@ghiscoding

ghiscoding commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

oops you're right, my bad, I did a git checkout of your branch again to do the review but forgot to git pull recent changes, dohhhh 🤦🏻‍♂️🤣 doing another review now

@ghiscoding

Copy link
Copy Markdown
Collaborator

ok it replied with:

Yes — after updating to 385258966, I consider the PR mergeable as-is, assuming normal CI is green.

The author addressed the earlier blockers:

I found no remaining merge-blocking issue and have no required code suggestions. git diff --check is clean; the production build and lint passed on this exact commit.

... so it looks good and ready to merge, I'll do that later today and maybe go with a new release too

Comment thread src/slick.grid.ts Outdated
@6pac

6pac commented Sep 1, 2026

Copy link
Copy Markdown
Owner

If you guys are happy with it, go ahead

@ghiscoding
ghiscoding merged commit e1a2d06 into 6pac:master Sep 1, 2026
3 checks passed
@ghiscoding

ghiscoding commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Thanks again for your contribution, I'll push a new release soon :)
See v5.20.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support header height auto-sizing

4 participants