Skip to content

fix(carousel): correct page count when rendered inside a Portal/Dialog#2997

Merged
segunadebayo merged 2 commits intomainfrom
copilot/fix-carousel-page-count-issue
Feb 28, 2026
Merged

fix(carousel): correct page count when rendered inside a Portal/Dialog#2997
segunadebayo merged 2 commits intomainfrom
copilot/fix-carousel-page-count-issue

Conversation

Copy link
Contributor

Copilot AI commented Feb 28, 2026

Carousel inside a Dialog/Portal computes snap points before the portal's DOM layout is complete, resulting in incorrect pageSnapPoints and unreachable slides (e.g., 10 pages calculated for 11 slides).

📝 Description

trackSlideResize only observed individual slide elements via ResizeObserver. When mounted inside a Portal, the container (item-group) resizes from zero to its actual dimensions as the dialog opens — but that resize was never observed, so SNAP.REFRESH was never triggered with correct layout data.

⛳️ Current behavior (updates)

SNAP.REFRESH (which recalculates pageSnapPoints) is only triggered by slide-level resize events. Container-level resizes — which happen when a Portal/Dialog completes layout — are silently ignored.

🚀 New behavior

The item-group container is now also observed by ResizeObserver. Any container resize (e.g., dialog opening, portal becoming visible) triggers SNAP.REFRESH, ensuring snap points are always computed from accurate layout dimensions.

// Before
const cleanups = itemEls.map((el) => resizeObserverBorderBox.observe(el, exec))

// After
const cleanups = [resizeObserverBorderBox.observe(el, exec), ...itemEls.map((el) => resizeObserverBorderBox.observe(el, exec))]

💣 Is this a breaking change (Yes/No):

No

📝 Additional Information

Original prompt

This section details on the original issue you should resolve

<issue_title>Carousel: incorrect page count when rendered inside a Dialog (Portal)</issue_title>
<issue_description># 🐛 Bug report

When a Carousel is rendered inside a Dialog (which uses a Portal), zag-js computes scroll snap points from an incomplete DOM layout. This causes the carousel to calculate fewer pages than there are actual slides, for example, 10 pages for 11 slides, making the last slide unreachable via prev/next navigation.

The root cause is that @zag-js/carousel measures the item-group's scroll dimensions synchronously on mount. When the carousel is inside a Portal with open animations (opacity, scale), the browser hasn't completed layout yet at mount time, so scrollWidth and related measurements are incorrect, leading to wrong pageSnapPoints.

💥 Steps to reproduce

  1. Render a Carousel with 11 slides inside a Dialog
  2. Open the dialog
  3. Click "Next" repeatedly to navigate through all slides
  4. Navigation stops at "Page 10 / 11" — slide 11 is unreachable

💻 Link to reproduction

CodeSandbox reproduction: https://codesandbox.io/p/sandbox/fxjkls

🧐 Expected behavior

The carousel should correctly calculate pageSnapPoints and pageCount even when rendered inside a Portal/Dialog. All 11 slides should be reachable via prev/next navigation.

🧭 Possible Solution

The snap-point calculation should be deferred until the Portal's DOM layout has been fully computed. Currently, measuring scrollWidth / offsetWidth on mount happens too early when inside a Portal with animations.

A fix in zag-js could involve recalculating snap points after the first layout paint (e.g., using a ResizeObserver on the item-group container, or deferring the initial measurement with requestAnimationFrame).

User-land workaround: Defer rendering the Carousel by one requestAnimationFrame after the Portal mounts:

const [layoutReady, setLayoutReady] = useState(false);

useEffect(() => {
  const frame = requestAnimationFrame(() => setLayoutReady(true));
  return () => cancelAnimationFrame(frame);
}, []);

if (!layoutReady) return null;

return <Carousel.Root>{/* slides */}</Carousel.Root>;

🌍 System information

Software Version(s)
Zag Version 1.34.1
Browser Chrome, Firefox, Safari (all affected)
Operating System Linux / macOS / Windows

📝 Additional information

The issue only manifests when the Carousel is inside a Portal (e.g., Chakra's Dialog) because the Portal DOM isn't laid out yet at mount time, especially combined with dialog open animations.
</issue_description>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

@changeset-bot
Copy link

changeset-bot bot commented Feb 28, 2026

🦋 Changeset detected

Latest commit: 06f6461

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

This PR includes changesets to release 84 packages
Name Type
@zag-js/carousel Patch
@zag-js/anatomy-icons Patch
@zag-js/anatomy Patch
@zag-js/core Patch
@zag-js/docs Patch
@zag-js/preact Patch
@zag-js/react Patch
@zag-js/solid Patch
@zag-js/svelte Patch
@zag-js/vanilla Patch
@zag-js/vue Patch
@zag-js/accordion Patch
@zag-js/angle-slider Patch
@zag-js/async-list Patch
@zag-js/avatar Patch
@zag-js/cascade-select Patch
@zag-js/checkbox Patch
@zag-js/clipboard Patch
@zag-js/collapsible Patch
@zag-js/color-picker Patch
@zag-js/combobox Patch
@zag-js/date-picker Patch
@zag-js/dialog Patch
@zag-js/drawer Patch
@zag-js/editable Patch
@zag-js/file-upload Patch
@zag-js/floating-panel Patch
@zag-js/hover-card Patch
@zag-js/image-cropper Patch
@zag-js/listbox Patch
@zag-js/marquee Patch
@zag-js/menu Patch
@zag-js/navigation-menu Patch
@zag-js/number-input Patch
@zag-js/pagination Patch
@zag-js/password-input Patch
@zag-js/pin-input Patch
@zag-js/popover Patch
@zag-js/presence Patch
@zag-js/progress Patch
@zag-js/qr-code Patch
@zag-js/radio-group Patch
@zag-js/rating-group Patch
@zag-js/scroll-area Patch
@zag-js/select Patch
@zag-js/signature-pad Patch
@zag-js/slider Patch
@zag-js/splitter Patch
@zag-js/steps Patch
@zag-js/switch Patch
@zag-js/tabs Patch
@zag-js/tags-input Patch
@zag-js/timer Patch
@zag-js/toast Patch
@zag-js/toggle-group Patch
@zag-js/toggle Patch
@zag-js/tooltip Patch
@zag-js/tour Patch
@zag-js/tree-view Patch
@zag-js/store Patch
@zag-js/types Patch
@zag-js/aria-hidden Patch
@zag-js/auto-resize Patch
@zag-js/collection Patch
@zag-js/color-utils Patch
@zag-js/utils Patch
@zag-js/date-utils Patch
@zag-js/dismissable Patch
@zag-js/dom-query Patch
@zag-js/file-utils Patch
@zag-js/focus-trap Patch
@zag-js/focus-visible Patch
@zag-js/highlight-word Patch
@zag-js/hotkeys Patch
@zag-js/i18n-utils Patch
@zag-js/interact-outside Patch
@zag-js/json-tree-utils Patch
@zag-js/live-region Patch
@zag-js/popper Patch
@zag-js/rect-utils Patch
@zag-js/remove-scroll Patch
@zag-js/scroll-snap Patch
@zag-js/stringify-state Patch
svelte-kit-starter 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

@vercel
Copy link

vercel bot commented Feb 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
zag-nextjs Ready Ready Preview Feb 28, 2026 0:50am
zag-solid Ready Ready Preview Feb 28, 2026 0:50am
zag-svelte Ready Ready Preview Feb 28, 2026 0:50am
zag-vue Ready Ready Preview Feb 28, 2026 0:50am
zag-website Ready Ready Preview Feb 28, 2026 0:50am

Request Review

…x page count in Portal/Dialog

Co-authored-by: anubra266 <30869823+anubra266@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix incorrect page count in carousel rendered inside dialog fix(carousel): correct page count when rendered inside a Portal/Dialog Feb 28, 2026
@segunadebayo segunadebayo marked this pull request as ready for review February 28, 2026 01:50
@segunadebayo segunadebayo merged commit 06ffe77 into main Feb 28, 2026
21 checks passed
@segunadebayo segunadebayo deleted the copilot/fix-carousel-page-count-issue branch February 28, 2026 10:17
@github-actions github-actions bot mentioned this pull request Feb 28, 2026
isBatak pushed a commit to isBatak/zag that referenced this pull request Feb 28, 2026
chakra-ui#2997)

* Initial plan

* fix(carousel): observe item-group container with ResizeObserver to fix page count in Portal/Dialog

Co-authored-by: anubra266 <30869823+anubra266@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: anubra266 <30869823+anubra266@users.noreply.github.com>
segunadebayo added a commit that referenced this pull request Mar 1, 2026
* feat: implement segmets getter

* feat: wip on segments

* feat: implement placeholder

* feat: fill in segments on value select

* feat: working on key events

* feat: replace local date formatter with global reference

* chore: fix comments

* feat: complete segment value increment/decrement functionality

* chore: some fixes

* feat: implement placeholderValue and reset segments on value clear

* feat: fix markValid

* feat: wip on backspace

* chore: remove extra pages

* feat: wip on segment delete

* chore: remove console log

* refactor(date-picker): simplify key handling and segment validation logic

* feat: initial segment input support

* feat: improve focus management

* chore: updates

* feat: fix input issures

* feat: clear placeholder data

* feat: implement home and end key actions

* feat: prevent default behavior for paste events in date picker input

* chore: export missing types

* feat: add placeholder display to date picker outputs and introduce datePickerWithSegmentsControls

* refactor: dedidcated package

* chore: update deps

* docs: add changeset

* revert: remove date-picker machine and example changes

* feat(date-field): integrate live region for segment announcements and add segment label utility

* test: fix e2e

* chore: compose

* chore: :granularity display

* fix: backspace delete

* chore: rename to date-input

* feat(-use-controls): add date support

* chore: add px ui script

* feat: add placeholder value display to date field component

* fix: rename dateField to dateInput

* feat: add test ArrowUp value starts from placeholder date

* feat: use el.fill instead of el.evaluate(

* feat: improve test

* feat: fix moving to next range input

* fix: improve "[input] Backspace clears day after clearing year" test

* fix: typo

* fix: implement clearValueIfAllSegmentsInvalid that fixes the flaky test

* feat: add granularity tests

* fix(date-input): prevent crash and stale segments when backspacing end date to empty

* fix(date-input): maek ArrowUp/Down on empty segments now starts from placeholderValue prop

* fix(date-input): use machine state for active segment in clearSegmentValue

* feat: wip on cycle fixes

* refactor(date-input): replace editingValue + validSegments with IncompleteDate

* chore: show displayValues in input range

* chore: update tests

* chore: update tests

* Version Packages (#2972)

ci(changesets): version packages

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* chore: add `data-placeholder-shown` to cascade select (#2975)

* fix(cascade-select): rename `data-focused` to `data-focus` for consistency (#2976)

* fix(cascade-select): rename `data-focused` to `data-focus` for consistency

* chore: update datepicker

* feat(date-picker): add non-Gregorian calendar support

* fix: docs tokens

* fix(combobox): return correct items in onValueChange for controlled usage (#2980)

* Initial plan

* fix: combobox onValueChange returns empty items array with controlled value

Co-authored-by: anubra266 <30869823+anubra266@users.noreply.github.com>

* test(combobox): add e2e tests for onValueChange items callback

Co-authored-by: anubra266 <30869823+anubra266@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: anubra266 <30869823+anubra266@users.noreply.github.com>

* chore(deps): lock file maintenance (#2982)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* chore: update pnpm

* chore: refactor eslint

* fix: toast types

* chore: select example

* chore: mv example

* build: prefer per-file build

* fix: popover sync layer-index

* fix(vanilla): merge props (#2984)

* fix(vanilla): styles prop with mergeProps

* chore: changeset

* chore(deps): update all non-major dependencies

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(*): support nested states (#2985)

* Initial plan

* feat: support nested states

Co-authored-by: segunadebayo <6916170+segunadebayo@users.noreply.github.com>

* docs(core): document nested states

Co-authored-by: segunadebayo <6916170+segunadebayo@users.noreply.github.com>

* docs(core): remove unsupported features

Co-authored-by: segunadebayo <6916170+segunadebayo@users.noreply.github.com>

* test: isolate nested state specs

Co-authored-by: segunadebayo <6916170+segunadebayo@users.noreply.github.com>

* test: strengthen nested coverage

Co-authored-by: segunadebayo <6916170+segunadebayo@users.noreply.github.com>

* chore: add changeset for nested states

Co-authored-by: segunadebayo <6916170+segunadebayo@users.noreply.github.com>

* refactor: code

* fix: typecheck and e2e

* chore: add example

* refactor: update

* test: refactoe

* test: setup vue tests

* test: setup svelte

* test: enrich

* test: even more

* refactor: use new nested state

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: segunadebayo <6916170+segunadebayo@users.noreply.github.com>
Co-authored-by: Segun Adebayo <joseshegs@gmail.com>

* docs: commuity page

* fix(colorpicker): nested state regression

* docs: next changelog

* Version Packages (#2977)

ci(changesets): version packages

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* docs: polish

* docs: update agents

* fix: color picker regression

* fix: packages/docs

* Version Packages (#2990)

ci(changesets): version packages

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* docs: next changelog

* fix: cjs build issues

* Version Packages (#2991)

ci(changesets): version packages

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* docs: changelog

* chore(deps): update dependency svelte to v5.53.5 [security] (#2992)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* fix(carousel): improve reliability

* fix(carousel): correct page count when rendered inside a Portal/Dialog (#2997)

* Initial plan

* fix(carousel): observe item-group container with ResizeObserver to fix page count in Portal/Dialog

Co-authored-by: anubra266 <30869823+anubra266@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: anubra266 <30869823+anubra266@users.noreply.github.com>

* chore: use transformValue in controlls

* chore: fix some tests

* feat(date-input): convert placeholder to CalendarDateTime for time granularities

* feat: update docs jsons

* feat(date-input): add BC era support and shouldForceLeadingZeros prop

* feat(date-input): add shouldForceLeadingZeros control and E2E tests

* fix(date-input): preserve entered segments when placeholderValue or granularity changes

* fix(date-input): preserve entered segments when granularity or placeholderValue changes

* perf(date-input): memoize segments computation

* docs(date-input): add component documentation and snippets

* feat(website): implement date-input showcase component

* feat(date-input): add groupCount API and improve reactivity

---------

Co-authored-by: Segun Adebayo <joseshegs@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Abraham <anubra266@gmail.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: anubra266 <30869823+anubra266@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Joost <81250358+jramke@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: segunadebayo <6916170+segunadebayo@users.noreply.github.com>
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.

Carousel: incorrect page count when rendered inside a Dialog (Portal)

3 participants