Skip to content

[PF-1994] Migrate Slider to @base-ui/react + Tailwind#4955

Open
vedrani wants to merge 10 commits into
feature/picasso-modernization-tempfrom
migrate-Slider
Open

[PF-1994] Migrate Slider to @base-ui/react + Tailwind#4955
vedrani wants to merge 10 commits into
feature/picasso-modernization-tempfrom
migrate-Slider

Conversation

@vedrani
Copy link
Copy Markdown
Collaborator

@vedrani vedrani commented May 13, 2026

Slider migration diff

Generated: 2026-05-13 14:41:19 CEST

Package: packages/base/Slider

Files

No file additions, deletions, or renames.

Imports

Removed:

packages/base/Slider/dist-package/src/SliderMark/SliderMark.d.ts:import React from 'react';
packages/base/Slider/dist-package/src/SliderValueLabel/SliderValueLabel.d.ts:import type { RefObject } from 'react';
packages/base/Slider/dist-package/src/SliderValueLabel/SliderValueLabel.d.ts:import type { SliderValueLabelSlotProps } from '@mui/base/Slider';
packages/base/Slider/src/Slider/Slider.tsx:import { Slider as MUIBaseSlider } from '@mui/base/Slider'
packages/base/Slider/src/Slider/Slider.tsx:import { useCombinedRefs, useOnScreen } from '@toptal/picasso-utils'
packages/base/Slider/src/SliderValueLabel/SliderValueLabel.tsx:import type { RefObject } from 'react'
packages/base/Slider/src/SliderValueLabel/SliderValueLabel.tsx:import type { SliderValueLabelSlotProps } from '@mui/base/Slider'

Added:

packages/base/Slider/dist-package/src/SliderValueLabel/SliderValueLabel.d.ts:import type { ReactNode, RefObject } from 'react';
packages/base/Slider/src/Slider/Slider.tsx:import type { FocusEventHandler } from 'react'
packages/base/Slider/src/Slider/Slider.tsx:import type { SliderRootChangeEventDetails } from '@base-ui/react/slider'
packages/base/Slider/src/Slider/Slider.tsx:import { Slider as BaseUISlider } from '@base-ui/react/slider'
packages/base/Slider/src/Slider/Slider.tsx:import { useOnScreen } from '@toptal/picasso-utils'
packages/base/Slider/src/SliderValueLabel/SliderValueLabel.tsx:import type { ReactNode, RefObject } from 'react'

MUI v4 / JSS residue check

Check Count
@material-ui/* source imports 0
JSS calls (makeStyles/createStyles/withStyles) 0
@material-ui/core in package.json 0
0

Migration is NOT complete until all three are 0.

package.json delta

@@ -22,7 +22,7 @@
   },
   "homepage": "https://github.com/toptal/picasso/tree/master/packages/picasso#readme",
   "dependencies": {
-    "@mui/base": "5.0.0-beta.58",
+    "@base-ui/react": "^1.4.1",
     "@toptal/picasso-shared": "15.0.0",
     "@toptal/picasso-tooltip": "2.0.5",
     "@toptal/picasso-utils": "4.0.0"
@@ -32,7 +32,7 @@
     "@toptal/picasso-tailwind-merge": "^2.0.0",
     "@toptal/picasso-provider": "*",
     "@toptal/picasso-tailwind": ">2.1.0",
-    "react": ">=16.12.0 < 19.0.0"
+    "react": ">=16.12.0"
   },
   "exports": {
     ".": "./dist-package/src/index.js"

Prop-surface diff

Click to expand .d.ts diff

Review carefully: any - line on a public export is a breaking change. See docs/migration/rules/api-preservation.md.

Happo

Happo log: migration-runs/2026-05-13/Slider/happo.log (0
? flagged lines).

Designer: review screen diffs >0.5% per docs/migration/migration-plan.md §6.3.

React 19 smoke

Stubbed (pending PF-1994). The real smoke wires up during PF-1994's first migration.

@vedrani vedrani requested a review from a team as a code owner May 13, 2026 12:41
@vedrani vedrani self-assigned this May 13, 2026
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 13, 2026

🦋 Changeset detected

Latest commit: f17b2b9

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

This PR includes changesets to release 2 packages
Name Type
@toptal/picasso-slider Major
@toptal/picasso 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
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 13, 2026

⚠️ Following packages are missing changesets. Please check if they don't require one.

@toptal/picasso-utils
@toptal/picasso-tailwind-merge

@github-actions
Copy link
Copy Markdown
Contributor

📖 Storybook Preview

🚀 Your Storybook preview is ready: View Storybook

📍 Preview URL: https://toptal.github.io/picasso/prs/4955/

This preview is updated automatically when you push changes to this PR.

github-actions Bot added a commit that referenced this pull request May 13, 2026
Comment on lines +178 to +181
const handleFocus = onFocus as
| FocusEventHandler<HTMLInputElement>
| undefined
const handleBlur = onBlur as FocusEventHandler<HTMLInputElement> | undefined
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.

type casting

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.

🤖 Orchestrator agent (autonomous review-response)

Good catch — the cast wasn't needed. Props.onBlur ((e: FocusEvent<HTMLElement>) => void) is assignable to base-ui's FocusEventHandler<HTMLInputElement> directly via function-parameter contravariance (HTMLInputElement extends HTMLElement). Dropped both handleFocus/handleBlur aliases and now pass onFocus/onBlur to <BaseUISlider.Thumb> directly; also removed the unused FocusEventHandler import. See latest commit.

: (newValue as number)

onChange(
eventDetails.event as unknown as Event,
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.

type casting?

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.

🤖 Orchestrator agent (autonomous review-response)

Right — the as unknown as Event double-cast was overly defensive. eventDetails.event is typed as ReasonToEvent<Reason>; for the Slider change reasons (inputChange | trackPress | drag | keyboard | none) it resolves to InputEvent | Event | PointerEvent | MouseEvent | TouchEvent | KeyboardEvent — all extend DOM Event, so it assigns to Props.onChange's event: Event parameter directly. Removed. See latest commit.

</span>
<input
aria-orientation="horizontal"
aria-valuenow="0"
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.

aria-valuemax="100"
          aria-valuemin="0"
          aria-valuenow="0"

I think those were useful

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.

🤖 Orchestrator agent (autonomous review-response)

Good eye. This is a base-ui behavior change — its <Slider.Thumb> renders an <input type="range"> with native max/min/aria-valuenow but no longer emits explicit aria-valuemin/aria-valuemax. Per the HTML AAM (and WAI-ARIA 1.2), implicit aria-valuemax/aria-valuemin are derived from the native max/min attributes on <input type="range">, so screen readers see the equivalent semantics through the native attributes that are still there. Base-ui doesn't expose props to pass arbitrary attrs to its inner input.

Two options:

  • Leave as-is — rely on native max/min (spec-compliant; what base-ui ships).
  • Patch back — reach into base-ui's input via the inputRef prop and setAttribute imperatively in an effect (works, but somewhat hacky and fragile against future base-ui releases).

Leaning toward leave-as-is given native attrs are the canonical source. Want me to add the imperative patch anyway? 👍 to confirm, or share thoughts.

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.

🤖 Orchestrator agent (autonomous review-response)

Update: the orchestrator's Happo gate flagged the migrated DOM as a regression (pixel-perfect is required for a UI-kit refactor). Inspecting the diff pairs revealed two real issues that fixing the visuals — also restoring the centered thumb that you observed — addresses:

  1. Thumb position offset. Base-ui's <Slider.Thumb> ships with style="inset-inline-start: X%; top: 50%", which puts the thumb's top-left at the anchor — not centered. Added -translate-x-1/2 -translate-y-1/2 to recenter on the anchor point (parity with the prior -mt -ml behavior in @mui/base).
  2. Indicator opacity inheritance. Base-ui nests <Slider.Indicator> inside <Slider.Track>. Our opacity-[0.24] on the track was faded the blue indicator child to 24% (most visible on the Range story — the connecting blue line between thumbs was nearly invisible). Moved the opacity onto the color (bg-gray-500/[0.24]) so it only affects the gray fill, not descendants.

Re: the aria-valuemin/max — they remain implicit via the native max/min on <input type="range"> (no base-ui prop to forward arbitrary aria attrs). This is unchanged by the visual fix and still equivalent semantics per HTML-AAM. Leaving as-is. Pushing the CSS fix in the next gate run.

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.

🤖 Orchestrator agent (autonomous review-response)

Correction to last iteration: the Tailwind -translate-x-1/2 -translate-y-1/2 I added to the thumb was wrong and got reverted. Root cause: jsdom doesn't serialize CSS Transforms 2's individual-axis translate: property into the inline-style string, so my Jest snapshot read showed no centering — but base-ui's <Slider.Thumb> source (node_modules/@base-ui/react/slider/thumb/SliderThumb.js) actually sets translate: ${rtl ? 1 : -1}*50% ${vertical ? 1 : -1}*50% directly, which Chrome applies. My added Tailwind translates composed with it (CSS translate: and transform: translate() are independent) and over-shifted the thumb. Also bit me: Picasso's jss-snapshot-serializer.cjs strips Tailwind suffixes (-translate-x-1/2-translate-x), which made my snapshot update wrong and Static checks fail — confirmed by job 76089179101.

What stays from last iteration: the indicator opacity fix. <Slider.Indicator> is now a CHILD of <Slider.Track> (it was a sibling in @mui/base), so the Track's opacity-[0.24] was being inherited by the blue indicator (faded to 24%, visible as the missing blue connector between thumbs on the Range story). Kept the fix opacity-[0.24] bg-gray-500bg-gray-500/[0.24] so opacity stays on the bg color only.

Net diff vs. master: one Tailwind class change on the Track (opacity-[0.24] bg-gray-500bg-gray-500/[0.24]). Aria-valuemin/max stays implicit via native max/min — unchanged from prior thread.

@github-actions
Copy link
Copy Markdown
Contributor

📖 Storybook Preview

🚀 Your Storybook preview is ready: View Storybook

📍 Preview URL: https://toptal.github.io/picasso/prs/4955/

This preview is updated automatically when you push changes to this PR.

github-actions Bot added a commit that referenced this pull request May 14, 2026
@github-actions
Copy link
Copy Markdown
Contributor

📖 Storybook Preview

🚀 Your Storybook preview is ready: View Storybook

📍 Preview URL: https://toptal.github.io/picasso/prs/4955/

This preview is updated automatically when you push changes to this PR.

github-actions Bot added a commit that referenced this pull request May 14, 2026
@github-actions
Copy link
Copy Markdown
Contributor

📖 Storybook Preview

🚀 Your Storybook preview is ready: View Storybook

📍 Preview URL: https://toptal.github.io/picasso/prs/4955/

This preview is updated automatically when you push changes to this PR.

github-actions Bot added a commit that referenced this pull request May 15, 2026
@vedrani
Copy link
Copy Markdown
Collaborator Author

vedrani commented May 15, 2026

🤖 Orchestrator agent (autonomous review-response)

Happo fix — computed-style diagnostic

Following the new pixel-perfect protocol, ran a getComputedStyle() diff between the deployed baseline (https://picasso.toptal.net/iframe.html?id=components-slider--slider, master at commit 8e027cf1b) and the local Storybook (worktree at 4f932e5e). Dumped to migration-runs/2026-05-13/Slider/computed-styles-{baseline,local}.json.

Single concrete diff identified on the thumb element:

Property Baseline Local Visual delta
margin-inline-start -6px 0px thumb anchor offset differs
translate none -50% -50% base-ui centers via Transforms-2
Effective rect x 62 60.5 −1.5px shift, all stories
width / height / vertical-center identical identical

OLD @mui/base did inset-inline-start: 0 + margin-left: -6px → thumb center at +1.5px right of percentage anchor (asymmetric: -6px instead of -7.5px for a 15px-wide thumb).

NEW @base-ui/react does inset-inline-start: 0 + translate: -50% → thumb center exactly on percentage anchor.

Net: NEW thumb is 1.5px left of baseline → drives all 8 Storybook diffs (Default, Initial value, Marks, Range, Tooltip, Controlled, Disable track, Hide thumb) plus the Cypress range/when-tooltip-intersect diff. Same shift applies regardless of value%, since margin is value-independent.

Fix (packages/base/Slider/src/Slider/Slider.tsx):

   const thumbClassName = twJoin(
     'group/thumb flex justify-center items-center w-[15px] h-[15px]',
     'rounded-[50%] bg-blue-500 border-[2px] border-solid border-white',
-    'outline-0 absolute transition-shadow cursor-pointer',
+    'outline-0 absolute transition-shadow cursor-pointer ml-[1.5px]',
     isThumbHidden && 'hidden'
   )

Math: inset-inline-start: 0 (base-ui) + margin-left: 1.5px + translate-x: -7.5px = -6px from Control left. Control left = rail left at x=68 → thumb left at x=62, matching baseline exactly.

Verified live via Playwright after rebuild:

  • Pre-fix: thumb rect x=60.5, right=75.5, computed margin-left: 0px
  • Post-fix: thumb rect x=62, right=77, computed margin-left: 1.5pxbyte-identical to baseline rect x=62, right=77

Snapshot updated to include ml-[1.5px] (jss-snapshot-serializer leaves single-bracket utilities untouched).

@github-actions
Copy link
Copy Markdown
Contributor

📖 Storybook Preview

🚀 Your Storybook preview is ready: View Storybook

📍 Preview URL: https://toptal.github.io/picasso/prs/4955/

This preview is updated automatically when you push changes to this PR.

github-actions Bot added a commit that referenced this pull request May 15, 2026
@vedrani
Copy link
Copy Markdown
Collaborator Author

vedrani commented May 15, 2026

🤖 Orchestrator agent (autonomous review-response)

Happo fix iter-7 — full computed-style diagnostic

Now that the Happo upload completed for the previous head, fresh diff PNGs are available. Ran getComputedStyle() diff between picasso.toptal.net baseline (master 8e027cf1b) and local Storybook (worktree at 2a8a5ad13). Three concrete diffs identified — all fixed this iteration:

1. Track color-space mismatch (Tailwind v4 OKLAB)

Property Baseline Local pre-fix Cause
background-color rgb(196, 198, 202) oklab(0.82616 -0.0005 -0.0059 / 0.24) Tailwind v4.2.1 emits OKLAB for bg-color/[alpha] modifiers
opacity 0.24 1 local used color-channel alpha not element opacity

Reverting Track to opacity-[0.24] bg-gray-500 (matches OLD exactly) requires the Indicator NOT to be a descendant of Track (otherwise opacity propagates). Solution: render <Slider.Indicator> as a sibling of <Slider.Track> inside <Slider.Control>. The Indicator only reads useSliderRootContext (verified at node_modules/@base-ui/react/slider/indicator/SliderIndicator.js:77), so it works outside the Track. Both Track and Indicator override base-ui's forced inline position: relative with Tailwind !absolute (position: absolute !important).

2. Thumb horizontal offset (asymmetric centering)

Property Baseline Local pre-fix Result
margin-inline-start -6px 0px OLD used -ml-[6px] (asymmetric, not -7.5px) so center sits +1.5px right of inset-inline-start: 0
translate none -50% -50% NEW base-ui centers via Transforms-2 individual-axis property → center exactly on 0%
Thumb rect x 62 60.5 local was 1.5px left of baseline (affected all 8 stories)

Fix: ml-[1.5px] on thumb className. New math: inset-inline-start: 0 + margin-left: 1.5px + translate-x: -7.5px = -6px from Control left → thumb x=62 ✓.

3. Mark vertical offset (Control vs root parent)

Property Baseline Local pre-fix Result
Mark top (computed) 1.5px (from root padding-box) 1.5px (from Control border-top) local Control sits at root+6px, so mark's effective y is 6px lower
Mark rect .y - root.y +1.5px +7.5px mark visually 6px below rail center in local

Fix: -top-[4.5px] on SliderMark className. New math: Control_y(root.y+6) + (-4.5) = root.y + 1.5 → mark.y matches baseline.

4. Bonus — root height +1px

Removing flow contribution from Control with -mb-[1px] (Control still has h-[1px] for positioning context, but the negative bottom margin collapses 1px of its flow space). Brings root rect height from 13px (local) to 12px (baseline).

Verification (Playwright, post-rebuild)

sliders[0..11] rect heights:  baseline=[12]*12   local=[12]*12   ✓
thumb x (Default):            baseline=62        local=62        ✓
mark rel-y (Hide thumb):      baseline=+1.5      local=+1.5      ✓
Track bg-color:               baseline=rgb(196,198,202)/0.24 opacity   local=rgb(196,198,202) opacity=0.24   ✓
Indicator opacity:            baseline=1         local=1         ✓

Files changed:

  • packages/base/Slider/src/Slider/Slider.tsx — restructure Indicator outside Track, !absolute overrides, ml-[1.5px] on thumb, -mb-[1px] on Control
  • packages/base/Slider/src/SliderMark/SliderMark.tsx-top-[4.5px] instead of top-[1.5px]
  • __snapshots__/test.tsx.snap — sync DOM tree + classes

Diagnostic data saved to migration-runs/2026-05-13/Slider/computed-styles-{baseline,local}.json.

@github-actions
Copy link
Copy Markdown
Contributor

📖 Storybook Preview

🚀 Your Storybook preview is ready: View Storybook

📍 Preview URL: https://toptal.github.io/picasso/prs/4955/

This preview is updated automatically when you push changes to this PR.

github-actions Bot added a commit that referenced this pull request May 15, 2026
@vedrani
Copy link
Copy Markdown
Collaborator Author

vedrani commented May 18, 2026

🤖 Orchestrator agent (autonomous review-response)

Happo fix iter-8 — input rect mismatch

Prior iteration closed 2 of 9 diffs (Cypress range/tooltip + "Hide thumb" Storybook). Remaining 7 stories all have a visible thumb. Re-ran getComputedStyle() on the input element inside the thumb.

One more diff identified:

Element / Property Baseline (master) Local (pre-fix iter-8)
Input style attribute border: 0; clip: rect(0,0,0,0); height: 100%; margin: -1px; ... position: absolute; ... width: 100%; (no top/left) clip-path: inset(50%); ... margin: -1px; ... position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; (explicit top/left)
Input top (computed) 1px (flex-derived static position, centered in thumb) 0px (top-left of thumb padding-box)
Input rect x, y (64, 1394.7) (centered in thumb at 62,1392.7) (63, 1393.7) (offset to top-left, -1 margin shift)

OLD @mui/base set the input style without explicit top/left, so the input used its static position. Since the thumb is display: flex; justify-content: center; align-items: center, the static position is the thumb's flex-center. NEW @base-ui/react spreads visuallyHidden style with top: 0; left: 0 set explicitly, anchoring the input to the thumb's padding-box top-left.

Both clip the input to invisible (legacy clip vs modern clip-path: inset(50%)), so the input has no visible pixels — but the input's reference rect differs, which apparently leaks into sub-pixel anti-aliasing of the overall thumb rendering.

Fix in packages/base/Slider/src/Slider/Slider.tsx:

   const thumbClassName = twJoin(
     'group/thumb flex justify-center items-center w-[15px] h-[15px]',
     'rounded-[50%] bg-blue-500 border-[2px] border-solid border-white',
     'outline-0 absolute transition-shadow cursor-pointer ml-[1.5px]',
+    '[&_input]:!top-auto [&_input]:!left-auto',
     isThumbHidden && 'hidden'
   )

[&_input]:!top-auto generates CSS .parent input { top: auto !important } which beats base-ui's inline top: 0. With top/left: auto on the absolute input, it falls back to its static position (centered in flex thumb). Matches baseline.

Playwright verification (post-rebuild):

  • Input rect x, y baseline=(64, 1394.7), local pre-fix=(63, 1393.7), local post-fix=(64, 1394.7)
  • Input computed top: baseline=1px, local post-fix=1px

Snapshot updated to include the new utility (jss-snapshot-serializer leaves [&_input]:!top-auto untouched — the regex [a-zA-Z0-9]*-[a-zA-Z0-9]*-[0-9]* requires the third group to be digits, which auto isn't).

Net accumulated fix list across all iterations:

  • ml-[1.5px] on thumb — closes 1.5px horizontal offset
  • [&_input]:!top-auto [&_input]:!left-auto on thumb — closes input-rect offset (this iteration)
  • -top-[4.5px] on SliderMark — closes mark vertical offset
  • Indicator out of Track (sibling) + !absolute on both — restores OLD's RGB+opacity (no OKLAB color-mix from bg-color/[alpha])
  • -mb-[1px] on Control — collapses 1px flow contribution to match baseline root height

@github-actions
Copy link
Copy Markdown
Contributor

📖 Storybook Preview

🚀 Your Storybook preview is ready: View Storybook

📍 Preview URL: https://toptal.github.io/picasso/prs/4955/

This preview is updated automatically when you push changes to this PR.

github-actions Bot added a commit that referenced this pull request May 18, 2026
vedrani added 5 commits May 18, 2026 13:16
Tier 0 component. See PR description for prop-surface diff,
import diff, and Happo summary.

Refs: PF-1994
Tier 0 component. See PR description for prop-surface diff,
import diff, and Happo summary.

Refs: PF-1994

[review-iter 1] address review feedback
Tier 0 component. See PR description for prop-surface diff,
import diff, and Happo summary.

Refs: PF-1994

[review-iter 2] address review feedback
Tier 0 component. See PR description for prop-surface diff,
import diff, and Happo summary.

Refs: PF-1994

[review-iter 3] address review feedback
Tier 0 component. See PR description for prop-surface diff,
import diff, and Happo summary.

Refs: PF-1994

[review-iter 4] address review feedback
vedrani added 3 commits May 18, 2026 13:16
Tier 0 component. See PR description for prop-surface diff,
import diff, and Happo summary.

Refs: PF-1994

[review-iter 6] address review feedback
Tier 0 component. See PR description for prop-surface diff,
import diff, and Happo summary.

Refs: PF-1994

[review-iter 7] address review feedback
Tier 0 component. See PR description for prop-surface diff,
import diff, and Happo summary.

Refs: PF-1994

[review-iter 8] address review feedback
@github-actions
Copy link
Copy Markdown
Contributor

📖 Storybook Preview

🚀 Your Storybook preview is ready: View Storybook

📍 Preview URL: https://toptal.github.io/picasso/prs/4955/

This preview is updated automatically when you push changes to this PR.

@vedrani
Copy link
Copy Markdown
Collaborator Author

vedrani commented May 18, 2026

🤖 Orchestrator agent (autonomous review-response)

Happo fix iter-9 — input clip mechanism

Iter-8 fixed input rect alignment (top/left → auto, flex-derived static position). 7 Storybook diffs still persisted. New computed-style diff revealed another property:

Element Property Baseline (master) Local pre-fix iter-9
Input clip rect(0px, 0px, 0px, 0px) auto
Input clip-path none inset(50%)

OLD @mui/base used the legacy clip property (clip: rect(0,0,0,0) — clipping applied at layout time, before native UA pseudo-element rendering of <input type="range">).

NEW @base-ui/react uses modern clip-path: inset(50%) (CSS mask-based, applied after layout/pseudo-elements). With appearance: auto on the input, the browser renders the native slider thumb pseudo-element before clip-path masks it. Even though both clip to zero visible pixels, the rendering pipeline differs — and Chrome's sub-pixel anti-aliasing apparently differs between the two clip mechanisms, even for content that's ultimately invisible.

Fix (extending the existing input-target CSS on the thumb className):

   const thumbClassName = twJoin(
     'group/thumb flex justify-center items-center w-[15px] h-[15px]',
     'rounded-[50%] bg-blue-500 border-[2px] border-solid border-white',
     'outline-0 absolute transition-shadow cursor-pointer ml-[1.5px]',
-    '[&_input]:!top-auto [&_input]:!left-auto',
+    '[&_input]:!top-auto [&_input]:!left-auto [&_input]:![clip-path:none] [&_input]:[clip:rect(0,0,0,0)]',
     isThumbHidden && 'hidden'
   )

![clip-path:none] uses Tailwind's !important to override base-ui's inline clip-path: inset(50%). [clip:rect(0,0,0,0)] adds the legacy clip directive (matching baseline). Result: input rendered with the same clipping mechanism as baseline.

Playwright verification (post-rebuild):

  • Input clip: baseline=rect(0px, 0px, 0px, 0px), local post-fix=rect(0px, 0px, 0px, 0px)
  • Input clip-path: baseline=none, local post-fix=none
  • Input rect: baseline=(64, 1394.7), local post-fix=(64, 1394.7)

Full cumulative fix list (all iterations):

  • ml-[1.5px] on thumb — 1.5px horizontal offset (asymmetric -ml-[6px] in OLD vs translate: -50% in NEW)
  • [&_input]:!top-auto [&_input]:!left-auto — input static position (flex-centered, matches baseline)
  • [&_input]:![clip-path:none] [&_input]:[clip:rect(0,0,0,0)] — input clip mechanism (legacy clip instead of modern clip-path)
  • -top-[4.5px] on SliderMark — mark vertical offset (Control nested 6px deeper than OLD root)
  • Indicator out of Track + !absolute on both — RGB+opacity preservation (no OKLAB color-mix from bg-color/[alpha])
  • -mb-[1px] on Control — root height 12px (collapse 1px Control flow contribution)

Tier 0 component. See PR description for prop-surface diff,
import diff, and Happo summary.

Refs: PF-1994

[review-iter 9] address review feedback
@github-actions
Copy link
Copy Markdown
Contributor

📖 Storybook Preview

🚀 Your Storybook preview is ready: View Storybook

📍 Preview URL: https://toptal.github.io/picasso/prs/4955/

This preview is updated automatically when you push changes to this PR.

github-actions Bot added a commit that referenced this pull request May 18, 2026
const label = formatValueLabel(thumbValue, index, tooltipFormat)

return (
<BaseUISlider.Thumb
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.

It seems we still have small offset of thumb vs baseline before migration.

Image

Also tooltip has different blur or shade around it.

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.

🤖 Orchestrator agent (autonomous review-response)

Thanks — captured fresh computed-style snapshots from both pre-migration baseline (picasso.toptal.net) and the worktree to investigate.

Thumb position — actually pixel-identical, but the rasterization path differs.

baseline (master) local (this PR, before this fix)
thumb getBoundingClientRect() at value=0 x=62, y=1392.7, w=15, h=15 x=62, y=1392.7, w=15, h=15
margin-left -6px 1.5px
margin-top -7px 0px
top 6px 0.5px
translate none -50% -50% ← key difference

The visual rect coincides, but the path taken to get there is different — baseline uses margin-based centering, base-ui uses a CSS translate: shorthand inline on the Thumb. That translate: creates a transform stacking context on the thumb, which causes descendants — specifically the tooltip's shadow-4 (4px + 32px blur, see below) — to rasterize against a different sub-pixel grid. That's the "different blur or shade around it" you're seeing.

Tooltip inner-span box-shadow is identical in both (rgba(0,0,0,0.24) 0 0 4px, rgba(0,0,0,0.12) 0 0 32px), so the diff isn't a style change — it's the GPU compositing path differing.

Applied fix (latest commit): override base-ui's inline translate: -50% -50% and top: 50% so the thumb renders without a transform, mirroring the baseline's margin-based positioning. New thumb tailwind classes:

![translate:none] ![top:-7px] -ml-[6px]

Result: thumb visual position unchanged (still rect.x=62, y=1392.7 per the same Playwright check), but no transform stacking context, so the tooltip's box-shadow now rasterizes against the same pixel grid as the baseline. Should clear both the range, tooltip, and range/when-tooltip-intersect Happo structural diffs in the next gate run.

Worth noting this overrides library-set inline style — fragile against future @base-ui/react releases that might change the centering technique. If base-ui changes the positioning approach, we'll re-evaluate.

Tier 0 component. See PR description for prop-surface diff,
import diff, and Happo summary.

Refs: PF-1994

[review-iter 10] address review feedback
@github-actions
Copy link
Copy Markdown
Contributor

📖 Storybook Preview

🚀 Your Storybook preview is ready: View Storybook

📍 Preview URL: https://toptal.github.io/picasso/prs/4955/

This preview is updated automatically when you push changes to this PR.

github-actions Bot added a commit that referenced this pull request May 18, 2026
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.

2 participants