Skip to content

fix(grid): grid custom group headers follow sticky start columns during horizontal scrolling - #3592

Open
michal-sanoma wants to merge 18 commits into
mainfrom
fix/3367_grid_custom_group_headers
Open

fix(grid): grid custom group headers follow sticky start columns during horizontal scrolling#3592
michal-sanoma wants to merge 18 commits into
mainfrom
fix/3367_grid_custom_group_headers

Conversation

@michal-sanoma

@michal-sanoma michal-sanoma commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes grouped grid headers so custom group headers follow sticky start columns during horizontal scrolling.

Changes

  • Add a Storybook reproduction for grouped rows with a custom group header and sticky columns
  • Keep group row cells full-width while making the inner group header sticky
  • Add a regression test for sticky group headers

Copy of this PR - #3370

BEFORE

before.mov

AFTER

after.mov

@michal-sanoma michal-sanoma self-assigned this Aug 12, 2026
@michal-sanoma michal-sanoma added the bug Something isn't working label Aug 12, 2026
@changeset-bot

changeset-bot Bot commented Aug 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5dfa538

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@sl-design-system/grid Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

@michal-sanoma
michal-sanoma requested a balanced review from Copilot August 12, 2026 11:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes grouped grid headers so custom headers remain aligned with sticky start columns during horizontal scrolling.

Changes:

  • Makes inner group headers sticky while preserving full-width group rows.
  • Adds a Storybook reproduction and regression test.
  • Includes unrelated formatting-only cleanups.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
.changeset/young-baboons-retire.md Records the grid patch.
packages/components/grid/src/grid.ts Implements sticky group headers.
packages/components/grid/src/grid.spec.ts Adds regression coverage.
packages/components/grid/src/stories/grouping.stories.ts Adds a reproduction story.
packages/components/grid/src/stories/selection.stories.ts Formatting cleanup.
packages/components/accordion/src/accordion.stories.ts Formatting cleanup.
packages/components/card/src/card.scss Formatting cleanup.
packages/components/icon/src/icon.scss Formatting cleanup.
tools/implement-design-plugin/agents/visual-validator.md Formats a Markdown table.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/components/grid/src/grid.spec.ts Outdated
Comment thread packages/components/grid/src/grid.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

@a11ymiko a11ymiko left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Collapsing all groups makes the last group row appear shorter than it should be. This happens on larger monitor.

On Mac screen the same last group row is higher than it should be.

Larger screen:

Screen.Recording.2026-08-13.at.08.41.04.mov

Mac screen:

Screen.Recording.2026-08-13.at.08.57.30.mov

@a11ymiko a11ymiko left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Mac screen "Netherlands" text is clipped in storybook view. User can't see the end of the word.

Image

@michal-sanoma

Copy link
Copy Markdown
Collaborator Author

Collapsing all groups makes the last group row appear shorter than it should be. This happens on larger monitor.

On Mac screen the same last group row is higher than it should be.

Collapsing all groups makes the last group row appear shorter than it should be. This happens on larger monitor.

On Mac screen the same last group row is higher than it should be.

Should be better now

@michal-sanoma
michal-sanoma requested a review from a11ymiko August 13, 2026 11:14
@a11ymiko

Copy link
Copy Markdown
Contributor

Collapsing all groups makes the last group row appear shorter than it should be. This happens on larger monitor.
On Mac screen the same last group row is higher than it should be.

Collapsing all groups makes the last group row appear shorter than it should be. This happens on larger monitor.
On Mac screen the same last group row is higher than it should be.

Should be better now

@michal-sanoma it is! 🙌

@a11ymiko
a11ymiko marked this pull request as ready for review August 13, 2026 11:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.

Suppressed comments (2)

packages/components/grid/src/group-header.scss:26

  • This fade is rendered whenever multiple sticky-start columns exist, including at scrollLeft === 0. The existing sticky-boundary fade in grid.scss:124-148 is intentionally gated by [scrollable-start], so group rows now show a boundary shadow before horizontal scrolling while the other rows do not. Propagate the grid's scrollable-start state to the group header and only enable this pseudo-element in that state; the regression test should cover both the initial and scrolled states.
:host(.sticky-start-last) [part='group-heading-wrapper']::after {
  background: linear-gradient(to right, rgb(79 79 79 / 10%), transparent);
  content: '';
  inline-size: 1rem;
  inset: 0 -1rem 0 auto;

packages/components/grid/src/grid.scss:320

  • When the final group is collapsed and the grid does not need a tfoot, this leaves the group's cell border directly above the existing tbody bottom border, producing a double-width bottom edge. Preserve the group row's height with a transparent cell border when there is no footer, while retaining the visible separator when a sticky scrollbar footer exists.
  tr:last-of-type:not([part~='group']) td {
    // Hide the bottom border of the last row; tbody already has a bottom border
    border-block-end: none;
  }

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.

Suppressed comments (4)

packages/components/grid/src/grid.spec.ts:899

  • These related DOM-query declarations should be part of the same multiline const statement, following the repository convention used in packages/components/grid/src/grid.ts:640-648.
      const groupHeader = el.renderRoot.querySelector<GridGroupHeader>(

packages/components/grid/src/grid.ts:1314

  • The suffix cannot be inferred from the number of visible sticky columns. If a leading sticky column is hidden, the remaining column can still have stickyOrder === 'last', while this assigns the group header sticky-start-first; the data-cell boundary then shows a shadow but the group header does not. Derive the suffix from the last visible sticky column's assigned order so both use the same boundary styling.
    return [`sticky-start-${columns.length > 1 ? 'last' : 'first'}`];

packages/components/grid/src/stories/grouping.stories.ts:290

  • Please combine these related declarations into one multiline const, following the repository convention used in packages/components/grid/src/grid.ts:640-648.
    const groupHeaderRenderer = (item: ListDataSourceGroupItem) => {

packages/components/grid/src/grid.spec.ts:873

  • Please combine these related declarations into one multiline const, following the repository convention used in packages/components/grid/src/grid.ts:640-648.

This issue also appears on line 899 of the same file.

      const groupHeaderRenderer = (item: ListDataSourceGroupItem<Person>) => {

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.

Suppressed comments (2)

packages/components/grid/src/grid.ts:1326

  • A column configured with width="0" is rendered as 100px by renderStyles() (col.width || '100'), but this calculation counts it as 0px. The sticky group heading then becomes narrower than the actual sticky cells. Use the same fallback semantics as the cell sizing rule.
    const inlineSize = columns.reduce((acc, { width }) => acc + (width ?? 100), 0);

packages/components/grid/src/grid.ts:1315

  • When the final sticky-start column is hidden, the preceding visible column can have no stickyOrder. This fallback then marks the group header sticky-start-last, while the visible td/th only have sticky-start. Because grid.scss uses table:not(:has(.sticky-start-last)), the group-header class suppresses the table fallback shadow even though no data/header boundary cell renders one. Recompute sticky order from visible columns, or use group-specific class names/selectors so the group marker cannot affect the table-wide check.
      `sticky-start-${columns.at(-1)?.stickyOrder ?? (columns.length > 1 ? 'last' : 'first')}`

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.

Suppressed comments (1)

packages/components/grid/src/grid.ts:1326

  • The 100px fallback is inconsistent with the sticky offset calculation below. With two sticky start columns where the first has width="0", that cell renders at 100px, but the second column still receives a 0px sticky offset while this wrapper is sized for both 100px widths. Once scrolled, the cells overlap and the group heading no longer matches their sticky boundary. Use the same normalized column width for both calculations (and cover this multi-column case in the fallback test).
    const inlineSize = columns.reduce((acc, { width }) => acc + (width || 100), 0);

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.

Comment thread packages/components/grid/src/grid.spec.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.

@michal-sanoma

Copy link
Copy Markdown
Collaborator Author

There is some unnecessary wrapping going on:
Also, in the non-sticky example the "add students" button is positioned on the right: Is it possible to keep it that way? > Maybe make the collapse button and title postion: sticky to the left, and the action button position:sticky to the right?
I think i got all the code you need for this in this screenshot: (of course you could extra wrappers to make it easier)

Really cool finding, I've improved it

@michal-sanoma
michal-sanoma requested a review from a11ymiko August 14, 2026 12:14
…group_headers

# Conflicts:
#	packages/components/grid/src/grid.ts
#	packages/components/grid/src/group-header.ts
@a11ymiko
a11ymiko marked this pull request as ready for review August 14, 2026 12:48
tbody.dispatchEvent(new Event('scroll'));

expect(getComputedStyle(headingWrapper!, '::after').content).to.equal('""');
expect(groupCell!.getBoundingClientRect().width).to.be.greaterThan(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a test, so I think we should be able to check explicit value, no value greater than.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

improved

expect(
getComputedStyle(row!.querySelector('td')!).getPropertyValue('border-block-end-color')
).not.to.equal('rgba(0, 0, 0, 0)');
expect(tfoot!.getBoundingClientRect().top).to.be.at.least(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a test, so I think we should be able to check explicit value.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one I would prefer to keep as is bc it's unstable when checking exact value on CI

}
};

export const StickyColumnsWithCustomGroupHeader: Story = {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the sticky group header is like 1px wider than the regular sticky column above or below?
Also border color is inconsistent.

Image

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

}
};

export const StickyColumnsWithCustomGroupHeader: Story = {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a weird, empty line below the last row?

Image

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

improved

}
};

export const StickyColumnsWithCustomGroupHeader: Story = {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's worth to add such example to Angular storybook as well.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

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

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[GRID] Sticky columns with grouping

6 participants