Steps to reproduce
Steps:
- Open this link to live example: https://codesandbox.io/p/devbox/mui-timeline-nextjs-spxvgc
- Navigate to the
/server route — you’ll see extra space appearing even though TimelineOppositeContent is present.
This issue occurs when TimelineItem is rendered inside a Server Component (e.g. a component without 'use client' in a Next.js App Router environment).
Current behavior
The ::before styling is incorrectly applied to TimelineItem even when TimelineOppositeContent is present, causing unwanted extra space at the start of the row.
Expected behavior
The ::before styling should not be applied to TimelineItem when TimelineOppositeContent is present.
Context
It appears that when the TimelineItem component is rendered on the server (i.e., without 'use client'), props.children is not being passed. As a result, React.Children.toArray(props.children) will always return [], incorrectly indicating that TimelineOppositeContent is not present.
|
React.Children.forEach(props.children, (child) => { |
|
if (isMuiElement(child, ['TimelineOppositeContent'])) { |
|
hasOppositeContent = true; |
|
} |
|
}); |
Since JSX children are not serializable, React does not pass them from server components to client components.
Proposed Solution
This can be addressed using pure CSS by leveraging the :has() selector to detect whether a <TimelineItem> contains a child with the MuiTimelineOppositeContent-root class (the default class applied to TimelineOppositeContent). Based on that, conditional styles can be applied.
I've been testing this approach locally and I can submit a PR shortly!
Your environment
System:
OS: macOS 14.5
Binaries:
Node: 22.16.0 - ~/.nvm/versions/node/v22.16.0/bin/node
npm: 10.9.2 - ~/.nvm/versions/node/v22.16.0/bin/npm
pnpm: 10.13.1 - ~/Library/pnpm/pnpm
Browsers:
Chrome: 138.0.7204.184
Edge: 138.0.3351.109
Safari: 17.5
npmPackages:
@mui/internal-babel-plugin-minify-errors: ^2.0.8-canary.3 => 2.0.8-canary.3
@mui/internal-babel-plugin-resolve-imports: ^2.0.7-canary.11 => 2.0.7-canary.11
@mui/internal-bundle-size-checker: ^1.0.9-canary.10 => 1.0.9-canary.10
@mui/internal-code-infra: 0.0.2-canary.22 => 0.0.2-canary.22
@mui/internal-docs-utils: workspace:^ => 2.0.1
@mui/internal-scripts: workspace:^ => 2.0.10
@mui/internal-test-utils: workspace:^ => 2.0.10
@mui/material: workspace:^ => 7.2.0
@mui/utils: workspace:^ => 7.2.0
@pigment-css/react: 0.0.30 => 0.0.30
@types/react: ^19.1.8 => 19.1.8
typescript: ^5.8.3 => 5.8.3
Search keywords: Timeline TimelineItem TimelineOppositeContent
Steps to reproduce
Steps:
/serverroute — you’ll see extra space appearing even thoughTimelineOppositeContentis present.This issue occurs when
TimelineItemis rendered inside a Server Component (e.g. a component without'use client'in a Next.js App Router environment).Current behavior
The
::beforestyling is incorrectly applied toTimelineItemeven whenTimelineOppositeContentis present, causing unwanted extra space at the start of the row.Expected behavior
The
::beforestyling should not be applied toTimelineItemwhenTimelineOppositeContentis present.Context
It appears that when the
TimelineItemcomponent is rendered on the server (i.e., without'use client'),props.childrenis not being passed. As a result,React.Children.toArray(props.children)will always return[], incorrectly indicating thatTimelineOppositeContentis not present.material-ui/packages/mui-lab/src/TimelineItem/TimelineItem.js
Lines 71 to 75 in 5052446
Since JSX children are not serializable, React does not pass them from server components to client components.
Proposed Solution
This can be addressed using pure CSS by leveraging the
:has()selector to detect whether a<TimelineItem>contains a child with theMuiTimelineOppositeContent-rootclass (the default class applied toTimelineOppositeContent). Based on that, conditional styles can be applied.I've been testing this approach locally and I can submit a PR shortly!
Your environment
Search keywords: Timeline TimelineItem TimelineOppositeContent