Skip to content

fix(ui-link,ui-breadcrumb,ui-table): avoid layout shifts on SSR hydration - #2724

Merged
matyasf merged 1 commit into
masterfrom
layout_shift_fixes
Sep 28, 2026
Merged

matyasf merged 1 commit into
masterfrom
layout_shift_fixes

Conversation

@matyasf

@matyasf matyasf commented Sep 21, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Link v2: derive the icon flex layout and the default medium size for variant from props in generateStyle, so the server render already has the final styles instead of getting them from makeStyles after mount.
  • Breadcrumb v2: drop TruncateText from Breadcrumb.Link and truncate with CSS (nowrap + ellipsis); a ResizeObserver only checks whether the text got clipped to enable the tooltip. The crumb li is display: block so the text can be clipped.
  • Table v2: Table.Row is now a function component that reads TableContext and computes its styles during render with useStyleNew, so stacked padding and hover borders are present on the server. A ref on Table.Row now points to the row element instead of the class instance.
  • Add a narrow, truncating Breadcrumb to the regression page.

Test Plan

Run the following test code in an app (e.g. replace docs app with this):
in Chrome's performance tab set CPU throttling to 20x and network to "slow 4G" and check that the example does not shift

<div>
  <Text>
    asdhe <Link variant="inline" size="medium" renderIcon={<DiamondInstUIIcon />} href="https://instructure.github.io/instructure-ui/">inline</Link> lasdant.
  </Text>
  <Text>
    Ifasdant:
    <Link variant="standalone" size="medium" renderIcon={<DiamondInstUIIcon />} href="https://instructure.github.io/instructure-ui/">standalone</Link>
  </Text>
  <Breadcrumb size="large" label="breadcrumb">
    <Breadcrumb.Link href="https://instructure.github.io/instructure-ui/">English 204</Breadcrumb.Link>
      <Breadcrumb.Link
        onClick={function () {
          console.log("This Breadcrumb.Link was clicked!")
        }}
      >
        Exploring John Updike
      </Breadcrumb.Link>
    <Breadcrumb.Link href="https://instructure.github.io/instructure-ui/">The Rabbit Novels</Breadcrumb.Link>
    <Breadcrumb.Link>Rabbit Is Rich</Breadcrumb.Link>
  </Breadcrumb>
      <Table caption={() => 'Top rated movies'} layout='stacked' hover='true'>
        <Table.Head>
          <Table.Row>
            <Table.ColHeader id="Rank">Rank</Table.ColHeader>
            <Table.ColHeader id="Title">Title</Table.ColHeader>
            <Table.ColHeader id="Year">Year</Table.ColHeader>
            <Table.ColHeader id="Rating">Rating</Table.ColHeader>
          </Table.Row>
        </Table.Head>
        <Table.Body>
          <Table.Row>
            <Table.RowHeader>1</Table.RowHeader>
            <Table.Cell>The Shawshank Redemption</Table.Cell>
            <Table.Cell>1994</Table.Cell>
            <Table.Cell>9.3</Table.Cell>
          </Table.Row>
          <Table.Row>
            <Table.RowHeader>2</Table.RowHeader>
            <Table.Cell>The Godfather</Table.Cell>
            <Table.Cell>1972</Table.Cell>
            <Table.Cell>9.2</Table.Cell>
          </Table.Row>
        </Table.Body>
      </Table>
</div>

Fixes INSTUI-5185

🤖 Generated with Claude Code

…tion

The server-rendered markup of three components differed from the mounted one, so the page jumped
during hydration:

- Breadcrumb: Breadcrumb.Link now truncates with CSS (nowrap + ellipsis) and only measures the
  text to decide whether the tooltip with the full text is available.
- Link: the flex layout when displaying an icon  and the default medium size for `variant`
  were only applied through `makeStyles` after mount. `generateStyle` now derives them from
  props, so the first render is already good.
- Table: Table.Row read `isStacked` and `hover` from TableContext in `makeStyles` after mount, so
  the stacked state padding and the hover borders were missing on the server. Row is now a
  functional component that computes its styles during render with `useStyleNew`. Note:
  A `ref` on Table.Row now points to the row element instead of the class instance.

INSTUI-5185

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@matyasf matyasf self-assigned this Sep 21, 2026
@github-actions

github-actions Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-09-28 08:43 UTC

github-actions Bot pushed a commit that referenced this pull request Sep 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Visual regression report

Cypress suite: ✅ Passing

Visual diff: ⚠️ Changes detected.

Status Count
Unchanged 91
Changed 5
New 0
Removed 0

Accessibility (axe): ✅ No violations.

📊 View full report — click a screenshot's ⚠ badge to see each violation boxed on the image, with the offending element named and contrast failures shown as color swatches.

Diff images (5)

badge-canvas.png — 1573 pixels differ

breadcrumb-canvas.png — 2847 pixels differ

breadcrumb-dark.png — 3270 pixels differ

breadcrumb-light.png — 3237 pixels differ

link-canvas.png — 75934 pixels differ

Baselines come from the visual-baselines branch. They refresh on every merge to master. The Cypress suite line covers the a11y and console-error assertions — a ❌ there means the suite found real issues even if the visual diff is clean.

@matyasf
matyasf requested review from HouseOfHawks, balzss and git-nandor and removed request for balzss September 21, 2026 20:56
@HouseOfHawks

Copy link
Copy Markdown
Collaborator

Video of a throttled Next.js SSR load - zero shift. Nice fixes! 🚀

Screen.Recording.2026-09-23.at.9.02.35.AM.mov

@matyasf
matyasf merged commit d90cd09 into master Sep 28, 2026
10 of 11 checks passed
@matyasf
matyasf deleted the layout_shift_fixes branch September 28, 2026 08:43
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.

3 participants