Skip to content

feat(ui): Add a Mosaic scroll area with scroll-driven edge fades - #9312

Open
maxyinger wants to merge 22 commits into
mainfrom
max/scroll-area-scrollbar-enhancements
Open

feat(ui): Add a Mosaic scroll area with scroll-driven edge fades#9312
maxyinger wants to merge 22 commits into
mainfrom
max/scroll-area-scrollbar-enhancements

Conversation

@maxyinger

@maxyinger maxyinger commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

Description

https://swingset-git-max-scroll-area-scrollbar-enhancements.clerkstage.dev/styles/scroll-area

Adds the Mosaic scroll area: a scrolling region that fades its content at whichever
edge still has something to reveal, and paints a matching scrollbar. It ships as
StyleX atoms — scrollAreaViewport(gutter?) and scrollAreaRoot — rather than a
component, since everything it does is CSS.

The fade is a mask driven by scroll-driven animations: no scroll listener, no
measurement, and paint-only, so it cannot shift content. The scrollbar is painted
through ::-webkit-scrollbar, which buys a real pixel width and a thumb colour per
interaction state. Both are tuned by global tokens (--cl-scroll-fade-*,
--cl-scrollbar-*) that retune every scrolling surface at once, and the fade can be
retired for a shadow or any other indicator by reading the two progress vars.

Internal for now, hence the empty changeset: ./mosaic is not in the exports map
and no shipped component applies the atoms, so nothing here is reachable from a
published entry point.

Swingset gains a Styles section whose Scroll Area page exercises every case.

Note

with this all being atomic stylex classes, it will be tougher for a user to target all scroll areas outside the theming variables. so something like change all scroll indicators to be shadows instead of a mask would be tough. worth considering adding some sort of stable class name to scrollers for ease of user css declarations and customization

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

@changeset-bot

changeset-bot Bot commented Aug 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: cef0fc7

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

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

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

vercel Bot commented Aug 1, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview Aug 1, 2026 1:17am
swingset Ready Ready Preview Aug 1, 2026 1:17am

Request Review

maxyinger and others added 22 commits July 31, 2026 19:09
A vertically scrolling region that fades its content at whichever edge still
has something to reveal. Composed as `ScrollArea.Root` + `ScrollArea.Viewport`.

The indicators are pure CSS with no runtime cost: two scroll-driven animations
write a progress var per edge, and a single four-stop mask gradient computes its
stops from them. Driving a number rather than the mask's own geometry means one
gradient covers both edges — no `mask-composite` — and leaves the progress vars
readable so a consumer can swap the treatment from a stylesheet alone.

The vars are `stylex.types.number`, so StyleX emits an `@property` registration
for each. That does two jobs: an unregistered custom property animates
discretely and would snap at 50% instead of tracking the scroll, and
`initial-value: 0` is what hides both indicators when the viewport isn't
scrollable — an inactive timeline leaves the vars at their initial value, so the
resting state is already the hidden one.

Only `animation-name` sits behind `@supports (animation-timeline: scroll())`.
A browser that ignores `animation-timeline` would otherwise run the animations
on the document timeline at the default `0s` duration, land on the end frame
immediately, and paint both fades permanently; with no name the rest is inert
and the mask resolves to fully opaque.

Because the fade is a mask rather than a sticky overlay element, it is
paint-only and cannot shift the content the way sticky pseudo-elements do.

`gutter` defaults to `auto`, matching CSS. `stable` is the opt-in for content
that can change height in place — a filterable list, a paginated table — where
crossing the overflow threshold would shift the rows sideways.

Scrollbar size is a theme token (`--cl-scrollbar-width`, default `thin`) rather
than a prop, since Mosaic has no reason to size scrollbars differently between
components. It is keyword-only by spec: `scrollbar-width` accepts
`auto | thin | none` and not a length.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds the story module and MDX page for `ScrollArea`, following the archetype A
compound layout: Example, Usage, Parts, Styling.

Five examples. `Default` and `NotScrollable` show that absent indicators are the
resting state rather than something switched off. `Gutter` toggles the content
across the overflow threshold, because `stable` and `auto` are identical while
the content permanently overflows — a side-by-side pair alone demonstrates
nothing, and the docs say so alongside the platform caveat, since neither value
does anything where scrollbars overlay.

`CustomIndicators` is the CSS-only override path: `mask-image: none` plus the
progress vars driving a pair of gradient overlays. Its scrim mixes from
`--cl-color-card-foreground` rather than hardcoding black — a black scrim
darkens a dark surface, which is indistinguishable from the mask it replaced, so
the example would have taught a bug in dark mode.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`--cl-scroll-area-fade-size`, `-fade-range` and `-scrollbar-inset` become
`--cl-scroll-fade-size`, `-range` and `-inset` in `tokens.stylex.ts`.

Every other token in Mosaic is global and category-named — `--cl-color-*`,
`--cl-radius-*`, `--cl-duration-*`, `--cl-scrollbar-width`. These three were
about to be the first `--cl-<component>-<thing>` family, and if each component
followed, theming Clerk would mean enumerating N components x M knobs rather
than learning one vocabulary. How soft the edge of a scrolling region is belongs
to the design language, not to one component; a component that needs different
values still sets the token on itself.

The progress vars stay component-named on purpose. They are per-element runtime
output that the animations overwrite on every scrolling element, so a `:root`
value would be meaningless — promoting them would imply they are settable when
setting them does nothing. Set versus read is the line.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Chrome and Firefox make an overflowing scroll container keyboard-focusable on
their own; Safari does not, so a keyboard-only user there cannot scroll the
region at all (WCAG 2.1.1). Leaving that to the caller meant every caller had to
know about a browser gap to get a baseline accessibility guarantee, which is the
component's responsibility rather than theirs.

The viewport now takes a tab stop exactly when the browsers themselves would:
when it overflows AND its content contains nothing focusable. The second half
carries as much weight as the first — a list whose rows are buttons or links is
already reachable, since tabbing into the content scrolls it, so a stop on the
container would be redundant. Reproducing the browsers' rule rather than
sniffing for Safari means this is a no-op wherever the browser already handles
it.

Both halves are observed rather than sampled once. A ResizeObserver watches the
viewport and each element child, because content growing past the threshold
leaves the viewport's own box unchanged and a MutationObserver wouldn't catch a
purely visual change like an image loading; a MutationObserver covers rows
gaining or losing interactivity. An explicit `tabIndex` always wins, and `-1`
opts out.

This is the component's only JavaScript. The JSDoc claim that nothing ran at
runtime is corrected accordingly — the indicators still cost nothing, with no
scroll listener and no measurement behind them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Brings `ScrollArea` in line with the rest of Mosaic, where every part is
polymorphic through `render`. A scrolling region often wants semantics of its
own — a list of members is a `<ul>`, a labelled region is a `<section>` — and
without this the caller had to choose between the component's styling and the
right element.

Both parts take it rather than just the viewport: a compound component where
only one half is polymorphic is a trap. On the viewport the rendered element has
to be able to establish a scroll box, since the overflow, mask and scroll
timelines all apply to whatever lands there.

`useRender` merges an array of refs, which also replaces the hand-rolled ref
composition the viewport needed to observe its own element while still honouring
the caller's ref.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nent

Everything the scroll area does is CSS, so wrapping it in a component only added
a DOM node to every scrolling surface and an API to version. `scrollAreaViewport`
returns the atoms for the element that scrolls and `scrollAreaRoot` styles a
positioned ancestor, so the styles ride on an element that already exists —
an `Item.Group`, a list, a panel body — rather than introducing one.

That also drops the `render` prop, since applying styles to your own element is
polymorphism by construction, and it removes the `.cl-scroll-area-*` classes: the
host element keeps its own slot class, and that stays the hook a theme targets.
The swingset examples put the atoms on an `Item.Group`, so the override story is
written against `.cl-item-group`.

The managed tab stop goes with it. It was the only JavaScript here, and
`tabindex` cannot be expressed as a style, so the Safari keyboard gap is now the
caller's to close — documented, with the rule the browsers themselves use.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…n page

A dedicated ScrollArea page made sense when there was a ScrollArea component.
Now that it ships as atoms, a component page for a thing that isn't a component
reads as a mistake, and the atoms are only meaningful applied to something.

`Item` gains a `Scrolling` example: a capped-height group of organizations with
the scroll surface spread onto the `Item.Group` itself, which is the shape a real
call site takes. The page absorbs what the ScrollArea docs carried that has no
other home — the theme tokens, the two progress vars, the override recipe, and
the two caveats worth knowing (the mask covers the scrollbar until a custom
scrollbar exists, and Safari's keyboard gap is the caller's to close).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A touch platform draws an overlay scrollbar there is no width or colour to apply
to, and thinning a target that is already hard to hit would be actively worse.
Gating appearance on `@media (pointer: fine)` leaves those platforms with the bar
they already draw. Shopify's `s-scroll-box` gates the same way.

`scrollbar-gutter` stays ungated: reserving space is a layout decision about the
surrounding content, not an appearance one, and the value that is right for a
list that can change height doesn't change with the pointer.

Forced-colors still wins inside the gate — StyleX nests the two at-rules and
triples the class, so the system scrollbar comes back where it has to.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Replaces `scrollbar-color` / `scrollbar-width` with the webkit pseudo-elements,
which is the only path that can express a per-interaction-state thumb colour and
a real pixel width. The browser derived the hover state from `scrollbar-color`
and got it backwards, lightening the thumb on hover; there is no syntax to
correct that on the standard path.

The two are mutually exclusive — a non-`auto` value for either standard property
makes a UA ignore the pseudo-elements — so this is one code path, not two.
Firefox implements neither and keeps its platform scrollbar.

`::-webkit-scrollbar-thumb` cannot transition its own properties, so the colour
is routed through an `@property`-registered var on the scroller and inherited
into the pseudo-element, which only reads it.

`--cl-scrollbar-width` becomes a length, and `--cl-scroll-fade-inset` is deleted:
now that we specify the lane's width, the mask derives its inset from it rather
than exposing a second name for the same number.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…f the lane

Three corrections to the webkit scrollbar, all found in the browser.

The mask still covered the scrollbar: StyleX 0.19 rewrites the argument of
`@supports selector(…)` with the same `:not(#\#)` specificity bump it applies to
real selectors, turning the query into
`selector(:not(#\#):not(#\#):not(#\#)::-webkit-scrollbar)` — false everywhere.
Verified in Chrome, where the honest form is true and the rewritten one is false.
Swapped for a property-based condition, which StyleX leaves alone.

Hover was reading the SCROLLER's `:hover`, so the thumb lit up whenever the
pointer was anywhere over the region. Both states now live on the thumb itself,
and the transition moves to the pseudo-element with them.

The lane goes to 8px carrying a 2px inset — a 4px pill — and the thumb starts
mixed most of the way toward the surface colour, so a bar this thin reads as a
hairline rather than a hard rule. In pixels, not rem: a scrollbar is chrome, and
should not scale with the text around it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Half of the Item page — 129 of 242 lines — documented the scroll surface, which
isn't Item and isn't a component. `Hooks` was already the precedent for a
non-component section (`use-data-table.stories.tsx` is `meta` alone, no story
exports), so `Styles` follows it: the scroll area gets a real page with room for
examples that don't fit on a component page. Item keeps the demo and a pointer.

This does revisit "Document the scroll surface on Item instead of its own page",
but that move was made because a COMPONENT page was the wrong home for something
that isn't a component. A Styles section answers that rather than reopening it.

New examples the old page had no room for: a surface with nothing to scroll (the
resting state costs nothing and needs no branch), `stable` vs `auto` gutter side
by side, and a retuned scrollbar.

Two rendering bugs surfaced by the first non-component, multi-word entry: the
sidebar wrapped every non-hook title in JSX, so this read `<Scroll Area />`, and
the breadcrumb only capitalised the first character, so it read "Scroll area".
The sidebar now picks its form from the layer, and the breadcrumb resolves the
registry's own `meta.title` — which also fixes `useDataTable`, previously shown
as "Use data table".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Blink runs no transition declared on `::-webkit-scrollbar-thumb` — the reason
Polaris declares its own on the scroller. Scoping hover to the thumb moved the
transition onto the pseudo-element with it, which silently killed the fade.

Putting it back on the scroller draws the line clearly: a change made ON THE
SCROLLER animates and the thumb inherits it, a change made on the thumb itself
can only snap. So `-hover` and `-active` are instant by construction, while a
consumer retargeting `--cl-scrollbar-thumb` from the region's `:hover` gets a
real fade. Confirmed by sampling painted pixels mid-transition.

Also declares `::-webkit-scrollbar-track` transparent. Opting into a custom
scrollbar makes the track ours, and leaving it undeclared falls back to the UA's
painting for that part — which shows through as a dark rail the moment the thumb
is less than opaque.

Adds the hover-reveal example this all came out of, and documents the one trap in
it: `transparent` is `rgba(0, 0, 0, 0)`, so fading out of the keyword drags the
thumb through dark half-transparent greys. The rest value is the reveal colour at
zero alpha instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…dius

A 12px corner against an 8px scrollbar lane reads as a mismatch — the bar runs
straight past the curve. `--cl-radius-inner` keeps the box out of the way of the
thing being demonstrated. Real surfaces would decorate around this.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Its three colours were all thumb states, which snap by construction — no choice
of colour makes them move. Added a scroller-driven step (rest amber → teal on
region hover) so the example shows both halves of the rule: a change made on the
scroller fades, a change made on the thumb does not. Verified by sampling
mid-transition pixels on the first and confirming the second jumps.

Notes in the description why the values are `oklch()` literals — one space keeps
the animated step well defined — and points at the `transparent` trap rather than
repeating it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The reveal jumped straight to full `--cl-color-neutral-faded`, which spent the
contrast up front and left the thumb's own hover and active with nowhere to go.
Scoping the override to `:not(:hover)` means reaching the region simply stops
hiding the bar, so the revealed state is Mosaic's ordinary rest colour and the
thumb's states read as a step up from it, exactly as on an unstyled scroll area.
Measured: invisible, then 78 → 101 → 126.

The theming example's one animated step runs at `--cl-duration-base`, which is
0.15s — over before you finish moving the pointer in, and on a scrollbar off to
one side that reads as nothing happening. Slowed to 0.6s in that example only.
Neither Item nor Avatar reads a duration token, so nothing else stretches.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The snippet still showed the two-rule form that revealed straight to full
`--cl-color-neutral-faded` — the loud version the example moved away from. It now
matches: one `:not(:hover)` rule hiding the default rest colour at zero alpha,
plus why hiding beats revealing (it leaves the thumb's own states their headroom).

The sample names the colour in an intermediate custom property for readability,
which the story inlines; checked that the two resolve identically before shipping
it as copy-paste.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds `--cl-scrollbar-thumb-idle`, a fourth colour sitting a notch below the base,
used while the pointer is elsewhere. The four now run quietest to loudest: idle,
base on region hover, then the thumb's own hover and active.

Idle → base is set on the scroller, so it is the one step that fades. That also
collapses the hover-reveal recipe to a single declaration —
`--cl-scrollbar-thumb-idle: oklch(from var(--cl-scrollbar-thumb) l c h / 0)` —
where the example previously needed a `:not(:hover)` rule of its own.

Adds `--cl-scrollbar-thumb-offset` (default 1px) to nudge the thumb toward the
content. A scrollbar's lane is placed by the browser at the inline end of the
padding box and takes no margin, offset, or transform, so the only way to move
anything is to move the thumb inside its lane: the offset comes off the inset on
one side and goes onto the other, shifting the pill without resizing it.
Logical properties, so the nudge stays on the content side in RTL, and the near
side is floored at 0 — a negative border width is invalid and would drop the
declaration back to `medium`, which looks like a broken thumb rather than a
slightly large token.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Shifting the thumb within its lane means unequal insets, and unequal insets
break the shape. `background-clip: content-box` clips the paint to the content
box using the INNER radius, which CSS derives per corner as the outer radius
minus that side's own border width. Different borders, different curvature on
each half of every cap. Measured on the 4px pill: a symmetric `35 76 76 35`
becomes `24 60 78 54`, and the outer edge squares off.

The geometry was right — the pill stayed 4px at every offset — which is why the
first pass at this looked fine. The cost was in the corners.

Reverted to a single uniform `border-width`, with the finding written down in
both the styles and the token docs so the next person doesn't retry it. Caps are
mirrored again on every row of the cap.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ample

Turns the prose-only "Replacing the fade" note into a running example. The
mask comes off and two pseudo-element scrims take over, each driving both
opacity and a translate from its edge's progress var, so a scrim slides out
from behind its edge rather than only fading in.

The scrim mixes from `--cl-color-card-foreground` so it reads as a shadow in
light mode and a glow in dark, where the hardcoded black the old snippet used
would have been invisible.

`mask-image` is dropped inline rather than from the demo's stylesheet: in dev,
swingset injects StyleX atoms with a `:not(#\#)` specificity bump that no
selector can outrank. The extracted production sheet puts the same atoms in a
cascade layer, so the documented CSS wins there on its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cuts the page down without dropping anything it was actually saying, and
strips the inline comments from the story bodies — those render in each
example's "View code" footer, unlike the JSDoc above them, which
`extractStorySource` leaves out.

Three things had gone stale:

- The recipe for keeping the scrollbar's lane without its bar set only
  `--cl-scrollbar-thumb: transparent`. `-idle` is a `color-mix` from that
  base, so it resolved to roughly 45% of the card colour rather than to
  nothing. It now sets both resting colours and says the thumb still paints
  faintly on hover.
- "Those two states switch instantly" had drifted two paragraphs away from
  the hover/active states it refers to, after the thumb-offset note landed
  between them.
- `:focus-within` lifts the thumb to its base colour just as the pointer
  does, which the token docs never mentioned.

Also renames `themedRows`, now that three examples share it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nothing in the scroll area is reachable from a published entry point: the
`exports` map exposes `./styles.css` but not `./mosaic`, so `scrollAreaViewport`
and `scrollAreaRoot` cannot be imported, and no shipped Mosaic component applies
the atoms. All a consumer's `styles.css` gains is a handful of inert `:root`
declarations and unused atoms.

Both changesets also described token churn that only ever happened inside this
branch — `--cl-scroll-fade-inset` being removed, `--cl-scrollbar-width` changing
from a keyword to a length — which would have read as breaking changes against a
state nobody was ever given.

The tokens become a real API the moment a shipped component scrolls with them or
`./mosaic` is exported; that is the change worth a changeset.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@pkg-pr-new

pkg-pr-new Bot commented Aug 1, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@9312

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@9312

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@9312

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@9312

@clerk/electron

npm i https://pkg.pr.new/@clerk/electron@9312

@clerk/electron-passkeys

npm i https://pkg.pr.new/@clerk/electron-passkeys@9312

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@9312

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@9312

@clerk/expo-google-signin

npm i https://pkg.pr.new/@clerk/expo-google-signin@9312

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@9312

@clerk/express

npm i https://pkg.pr.new/@clerk/express@9312

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@9312

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@9312

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@9312

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@9312

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@9312

@clerk/react

npm i https://pkg.pr.new/@clerk/react@9312

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@9312

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@9312

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@9312

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@9312

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@9312

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@9312

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@9312

commit: cef0fc7

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-08-01T01:17:00.106Z

Summary

Metric Count
Packages analyzed 19
Packages with changes 0
🔴 Breaking changes 0
🟡 Non-breaking changes 0
🟢 Additions 0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on cef0fc7.

@maxyinger
maxyinger force-pushed the max/scroll-area-scrollbar-enhancements branch from 029359d to cef0fc7 Compare August 1, 2026 01:14
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a Mosaic Scroll Area styling API with scrollbar tokens, scroll-fade variables, gutters, edge indicators, focus states, exports, and tests. Adds Scroll Area and Item Scrolling stories with reference documentation. Registers the new Styles documentation group and updates Swingset navigation labels and breadcrumbs. Documents Styles and Hooks as non-component layers. Adds a patch-release changeset.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • clerk/javascript#9234: Adds the Item component that this change extends with scrolling stories and documentation.
  • clerk/javascript#9311: Adds the Scroll Area atoms, tokens, exports, tests, and stories extended by this change.

Suggested reviewers: alexcarpenter, austincalvelage

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.73% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the new Mosaic scroll area and its scroll-driven edge fades.
Description check ✅ Passed The description directly explains the Mosaic scroll area, its StyleX APIs, scrolling behavior, tokens, and documentation changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 4

🧹 Nitpick comments (5)
packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts (2)

273-303: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add an explicit return type and an @returns tag to scrollAreaViewport.

scrollAreaViewport is a public API export. The coding guidelines require explicit return types for public APIs and JSDoc with @returns. The inferred tuple type is also part of the published surface, so naming it prevents accidental widening.

♻️ Proposed change
+ * `@returns` The StyleX atoms to spread onto the scrolling element.
  */
-export function scrollAreaViewport(gutter: ScrollAreaGutter = 'auto') {
+export function scrollAreaViewport(gutter: ScrollAreaGutter = 'auto'): readonly stylex.StyleXStyles[] {
   return [

Alternatively, declare a named exported type for the returned tuple so consumers can reference it.

As per coding guidelines: "Always define explicit return types for functions, especially public APIs" and "Document functions with JSDoc comments including @param, @returns, @throws, and @example tags".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts` around
lines 273 - 303, Update the public scrollAreaViewport function with an explicit,
stable return type for its StyleX atom tuple, preferably using a named exported
type consumers can reference, and add an `@returns` tag to its existing JSDoc
describing the returned styles. Preserve the current tuple contents and
ordering.

Sources: Coding guidelines, Learnings


201-205: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Consider the paint cost of a mask on every scroll surface.

maskImage on the scroller forces a compositing layer and repaints the masked region on each scroll frame. For long Item.Group lists this is measurable, and a mask on a scroll container also clips position: sticky descendants inside it. Confirm the behavior on a long list before this becomes the default for every Mosaic scroll surface.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts` around
lines 201 - 205, Reassess the mask-based styling in the scroll-area style
definition, especially the mask property using maskImage, and avoid applying it
by default to every Mosaic scroll surface due to scroll repaint and
sticky-descendant clipping costs. Validate the behavior with a long Item.Group
list and preserve the existing scroll-surface appearance through a lower-cost or
opt-in approach.
packages/ui/src/mosaic/components/scroll-area/index.ts (1)

1-3: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Barrel file, flagged for awareness only.

The coding guidelines advise against index.ts re-export barrels because of circular-dependency risk. This file matches the existing Mosaic pattern, and no cycle exists today: scroll-area.styles.ts imports ../../tokens.stylex directly rather than through a barrel. The split between value exports and the export type line is correct for tree-shaking. No change is required unless the team wants to drop the barrel pattern across Mosaic.

As per coding guidelines: "Avoid barrel files (index.ts re-exports) as they can cause circular dependencies".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/mosaic/components/scroll-area/index.ts` around lines 1 - 3,
No change is required in the scroll-area barrel exports; retain the existing
value exports and separate export type for consistency with the Mosaic pattern.
Only replace this index.ts re-export pattern if the team is undertaking a
broader removal of Mosaic barrel files.

Source: Coding guidelines

packages/ui/src/mosaic/components/scroll-area/scroll-area.test.ts (1)

8-21: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert atom identity rather than the atom count.

toHaveLength(7) passes if an atom is replaced by a different one, and it fails on any deliberate addition without saying what changed. If styles.mask or styles.scrollbar is dropped from the tuple, the count test is the only guard and it reports a number instead of the missing behavior. Assert membership of each atom, and assert that the stable variant carries the gutter rather than only differing from auto.

💚 Proposed test change
   it('composes the viewport atoms into one spreadable set', () => {
-    expect(scrollAreaViewport()).toHaveLength(7);
-    expect(scrollAreaRoot).toBeDefined();
+    const atoms = scrollAreaViewport();
+    expect(atoms).toHaveLength(7);
+    // Every atom is distinct, so a dropped or duplicated slot fails here.
+    expect(new Set(atoms).size).toBe(atoms.length);
+    expect(atoms.every(Boolean)).toBe(true);
+    expect(scrollAreaRoot).toBeDefined();
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/mosaic/components/scroll-area/scroll-area.test.ts` around
lines 8 - 21, Update the scrollAreaViewport tests to assert that the returned
atom set contains each expected viewport atom, including styles.mask and
styles.scrollbar, instead of relying on toHaveLength(7). In the gutter test,
verify that the stable variant contains the stable gutter atom directly, while
preserving the existing default-auto assertion.
packages/ui/src/mosaic/tokens.stylex.ts (1)

90-135: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Design rationale belongs in the Swingset reference page, not in inline comments. Across the three implementation files this cohort adds roughly 200 lines of prose rationale to about 130 lines of code. The content is valuable — the pixel-versus-rem argument, the four thumb states, the Blink transition limitation, the Gecko @supports workaround, the forced-colors decision — but the coding guidelines require one terse line per warranted comment. This PR already adds packages/swingset/src/stories/scroll-area.mdx, which is the correct home for it, and prose there stays discoverable to consumers instead of only to readers of the source.

  • packages/ui/src/mosaic/tokens.stylex.ts#L90-L135: move the scrollbar-token rationale (pixel scale, four-state derivation, no-nudge decision, pointer: fine gating) to the MDX page and keep one line per token group. Apply the same to the scroll-fade block on lines 152-166.
  • packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts#L116-L144: move the 29-line scrollbar block comment to the MDX page and keep a single line noting why every declaration repeats { default: null, '@media (pointer: fine)': … }. Apply the same treatment to the blocks at lines 16-26, 41-50, 77-91, 206-218, and 227-232. Keep the eslint-disable justifications, which are load-bearing and already terse.
  • packages/ui/src/mosaic/components/scroll-area/scroll-area.vars.stylex.ts#L3-L18: move the @property registration explanation to the MDX page and keep one line stating that the typed vars exist so StyleX emits @property with initial-value: 0.

As per coding guidelines: "Keep code comments minimal. Add comments only when critical to explain why a non-obvious change was made; never restate code behavior, and keep warranted comments to one terse line rather than a verbose multi-line block."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/mosaic/tokens.stylex.ts` around lines 90 - 135, Move verbose
design rationale from packages/ui/src/mosaic/tokens.stylex.ts#L90-L135 and
`#L152-L166`,
packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts#L116-L144,
`#L16-L26`, `#L41-L50`, `#L77-L91`, `#L206-L218`, and `#L227-L232`, and
packages/ui/src/mosaic/components/scroll-area/scroll-area.vars.stylex.ts#L3-L18
to packages/swingset/src/stories/scroll-area.mdx. Retain only one terse,
warranted comment per token or style group: explain the pointer-fine gating,
repeated media-query declarations, and typed variables emitting `@property` with
initial-value 0; preserve the existing eslint-disable justifications.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.changeset/lazy-pans-tickle.md:
- Line 17: Align both release notes with the shipped scrollbar token contract:
in .changeset/lazy-pans-tickle.md at line 17, identify the transparent
--cl-scrollbar-thumb-idle value as an override or document its color-mix
default; in .changeset/mosaic-scroll-area.md at lines 19-20, document
--cl-scrollbar-width as an 8px length and remove --cl-scroll-fade-inset from the
new-token list.

In `@packages/swingset/src/lib/registry.ts`:
- Around line 98-105: Update the scroll-area story registration by importing the
exported ShadowIndicators symbol alongside the existing ScrollArea story imports
and adding it to scrollAreaModule with the other registered stories. Keep
registry.ts as the single source of truth for all six scroll-area stories.

In `@packages/swingset/src/stories/item.stories.tsx`:
- Around line 387-390: Update the Avatar.Image in the Item story to use an empty
alt value because Item.Title already provides the organization name; follow the
existing pattern in the corresponding Item MDX example and preserve the current
image source.

In `@packages/ui/src/mosaic/components/scroll-area/scroll-area.test.ts`:
- Around line 51-58: Split the `--cl-scroll-fade-inset` absence assertion out of
the `keeps the thumb colour carrier out of the public token namespace` test into
a separate test focused on `scrollFadeVars`. Keep the existing
`scrollbarThumbVars` assertion in the thumb-colour test and preserve both
expectations unchanged.

---

Nitpick comments:
In `@packages/ui/src/mosaic/components/scroll-area/index.ts`:
- Around line 1-3: No change is required in the scroll-area barrel exports;
retain the existing value exports and separate export type for consistency with
the Mosaic pattern. Only replace this index.ts re-export pattern if the team is
undertaking a broader removal of Mosaic barrel files.

In `@packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts`:
- Around line 273-303: Update the public scrollAreaViewport function with an
explicit, stable return type for its StyleX atom tuple, preferably using a named
exported type consumers can reference, and add an `@returns` tag to its existing
JSDoc describing the returned styles. Preserve the current tuple contents and
ordering.
- Around line 201-205: Reassess the mask-based styling in the scroll-area style
definition, especially the mask property using maskImage, and avoid applying it
by default to every Mosaic scroll surface due to scroll repaint and
sticky-descendant clipping costs. Validate the behavior with a long Item.Group
list and preserve the existing scroll-surface appearance through a lower-cost or
opt-in approach.

In `@packages/ui/src/mosaic/components/scroll-area/scroll-area.test.ts`:
- Around line 8-21: Update the scrollAreaViewport tests to assert that the
returned atom set contains each expected viewport atom, including styles.mask
and styles.scrollbar, instead of relying on toHaveLength(7). In the gutter test,
verify that the stable variant contains the stable gutter atom directly, while
preserving the existing default-auto assertion.

In `@packages/ui/src/mosaic/tokens.stylex.ts`:
- Around line 90-135: Move verbose design rationale from
packages/ui/src/mosaic/tokens.stylex.ts#L90-L135 and `#L152-L166`,
packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts#L116-L144,
`#L16-L26`, `#L41-L50`, `#L77-L91`, `#L206-L218`, and `#L227-L232`, and
packages/ui/src/mosaic/components/scroll-area/scroll-area.vars.stylex.ts#L3-L18
to packages/swingset/src/stories/scroll-area.mdx. Retain only one terse,
warranted comment per token or style group: explain the pointer-fine gating,
repeated media-query declarations, and typed variables emitting `@property` with
initial-value 0; preserve the existing eslint-disable justifications.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 27edef85-c1e1-453e-b133-8b3c69b9147b

📥 Commits

Reviewing files that changed from the base of the PR and between fdadaa2 and 029359d.

📒 Files selected for processing (18)
  • .changeset/gentle-hoops-relax.md
  • .changeset/lazy-pans-tickle.md
  • .changeset/mosaic-scroll-area.md
  • packages/swingset/CLAUDE.md
  • packages/swingset/src/components/ClientRoot.tsx
  • packages/swingset/src/components/DocsViewer.tsx
  • packages/swingset/src/components/app-sidebar.tsx
  • packages/swingset/src/lib/registry.ts
  • packages/swingset/src/stories/item.mdx
  • packages/swingset/src/stories/item.stories.tsx
  • packages/swingset/src/stories/scroll-area.mdx
  • packages/swingset/src/stories/scroll-area.stories.tsx
  • packages/ui/src/mosaic/components/scroll-area/index.ts
  • packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts
  • packages/ui/src/mosaic/components/scroll-area/scroll-area.test.ts
  • packages/ui/src/mosaic/components/scroll-area/scroll-area.vars.stylex.ts
  • packages/ui/src/mosaic/styles/index.ts
  • packages/ui/src/mosaic/tokens.stylex.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)

Comment thread .changeset/lazy-pans-tickle.md Outdated
Comment on lines +98 to +105
import {
Default as ScrollAreaDefault,
Gutter as ScrollAreaGutter,
HoverReveal as ScrollAreaHoverReveal,
meta as scrollAreaMeta,
NotScrollable as ScrollAreaNotScrollable,
ThemedScrollbar as ScrollAreaThemedScrollbar,
} from '../stories/scroll-area.stories';

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.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find consumers that enumerate story-module keys generically, beyond `.meta`.
rg -n 'Object\.(keys|entries|values)\(' packages/swingset/src --glob '*.{ts,tsx}' -C3
rg -n 'storyModule\b' packages/swingset/src --glob '*.{ts,tsx}' -C3

Repository: clerk/javascript

Length of output: 10068


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== registry imports and scrollAreaModule =="
sed -n '90,110p;220,236p' packages/swingset/src/lib/registry.ts

echo
echo "== scroll-area stories exports and shadow indicators =="
rg -n "^export (const|function|interface|type|default) (ShadowIndicators|meta|Default|Gutter|HoverReveal|NotScrollable|ThemedScrollbar)" packages/swingset/src/stories/scroll-area.stories.tsx -C 2 || true

echo
echo "== registry.ts StoryModule type =="
rg -n "interface StoryModule|type StoryModule|meta:" packages/swingset/src/lib/registry.ts -C 3

echo
echo "== docsViewer registry usage =="
rg -n "getModule|docsModules|registry" packages/swingset/src/ -g '*.ts' -g '*.tsx' -C 2

Repository: clerk/javascript

Length of output: 12457


Register the missing ShadowIndicators story.

scroll-area.stories.tsx exports ShadowIndicators, but registry.ts imports and registers only the other five scroll area stories. Add ShadowIndicators to both the import and scrollAreaModule so lib/registry.ts remains the single source of truth for the story components.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/swingset/src/lib/registry.ts` around lines 98 - 105, Update the
scroll-area story registration by importing the exported ShadowIndicators symbol
alongside the existing ScrollArea story imports and adding it to
scrollAreaModule with the other registered stories. Keep registry.ts as the
single source of truth for all six scroll-area stories.

Source: Path instructions

Comment on lines +387 to +390
<Avatar.Image
src='https://github.com/clerk.png'
alt={name}
/>

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Avoid duplicating the organization name in the accessible label.

Item.Title already renders name inside the button. alt={name} can cause assistive technology to announce the organization name twice. Use alt='', as shown in packages/swingset/src/stories/item.mdx Line 74, unless the image conveys information different from the title.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/swingset/src/stories/item.stories.tsx` around lines 387 - 390,
Update the Avatar.Image in the Item story to use an empty alt value because
Item.Title already provides the organization name; follow the existing pattern
in the corresponding Item MDX example and preserve the current image source.

Comment on lines +51 to +58
// The counterpart to the assertion above: `--_cl-` is the marker for plumbing, so it must not
// drift into the themable `--cl-` namespace the way a rename easily could.
it('keeps the thumb colour carrier out of the public token namespace', () => {
expect(scrollbarThumbVars).toMatchObject({
'--_cl-scrollbar-thumb-color': 'var(--_cl-scrollbar-thumb-color)',
});
expect(Object.keys(scrollFadeVars)).not.toContain('--cl-scroll-fade-inset');
});

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Move the --cl-scroll-fade-inset assertion into its own test.

The test name states that it checks the thumb-colour carrier namespace. Line 57 asserts that the removed --cl-scroll-fade-inset token is absent from scrollFadeVars, which is a separate contract. If line 57 fails, the reported test name points a reader at the wrong token family.

💚 Proposed split
   it('keeps the thumb colour carrier out of the public token namespace', () => {
     expect(scrollbarThumbVars).toMatchObject({
       '--_cl-scrollbar-thumb-color': 'var(--_cl-scrollbar-thumb-color)',
     });
+  });
+
+  // The mask derives its inset from `--cl-scrollbar-width`, so the old knob must stay gone.
+  it('no longer exposes the removed fade inset token', () => {
     expect(Object.keys(scrollFadeVars)).not.toContain('--cl-scroll-fade-inset');
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// The counterpart to the assertion above: `--_cl-` is the marker for plumbing, so it must not
// drift into the themable `--cl-` namespace the way a rename easily could.
it('keeps the thumb colour carrier out of the public token namespace', () => {
expect(scrollbarThumbVars).toMatchObject({
'--_cl-scrollbar-thumb-color': 'var(--_cl-scrollbar-thumb-color)',
});
expect(Object.keys(scrollFadeVars)).not.toContain('--cl-scroll-fade-inset');
});
// The counterpart to the assertion above: `--_cl-` is the marker for plumbing, so it must not
// drift into the themable `--cl-` namespace the way a rename easily could.
it('keeps the thumb colour carrier out of the public token namespace', () => {
expect(scrollbarThumbVars).toMatchObject({
'--_cl-scrollbar-thumb-color': 'var(--_cl-scrollbar-thumb-color)',
});
});
// The mask derives its inset from `--cl-scrollbar-width`, so the old knob must stay gone.
it('no longer exposes the removed fade inset token', () => {
expect(Object.keys(scrollFadeVars)).not.toContain('--cl-scroll-fade-inset');
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/mosaic/components/scroll-area/scroll-area.test.ts` around
lines 51 - 58, Split the `--cl-scroll-fade-inset` absence assertion out of the
`keeps the thumb colour carrier out of the public token namespace` test into a
separate test focused on `scrollFadeVars`. Keep the existing
`scrollbarThumbVars` assertion in the thumb-colour test and preserve both
expectations unchanged.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/swingset/src/stories/scroll-area.stories.tsx (1)

28-58: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add explicit return types to the story components.

  • packages/swingset/src/stories/scroll-area.stories.tsx#L28-L58: Add : React.JSX.Element to OrganizationRow.
  • packages/swingset/src/stories/scroll-area.stories.tsx#L64-L93: Add : React.JSX.Element to Default.
  • packages/swingset/src/stories/scroll-area.stories.tsx#L96-L115: Add : React.JSX.Element to NotScrollable.
  • packages/swingset/src/stories/scroll-area.stories.tsx#L123-L182: Add : React.JSX.Element to Gutter.
  • packages/swingset/src/stories/scroll-area.stories.tsx#L208-L228: Add : React.JSX.Element to HoverReveal.
  • packages/swingset/src/stories/scroll-area.stories.tsx#L235-L263: Add : React.JSX.Element to ThemedScrollbar.
  • packages/swingset/src/stories/scroll-area.stories.tsx#L279-L325: Add : React.JSX.Element to ShadowIndicators.

As per coding guidelines, “Always define explicit return types for functions, especially public APIs.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/swingset/src/stories/scroll-area.stories.tsx` around lines 28 - 58,
Add explicit React.JSX.Element return types to the OrganizationRow, Default,
NotScrollable, Gutter, HoverReveal, ThemedScrollbar, and ShadowIndicators
components in packages/swingset/src/stories/scroll-area.stories.tsx at ranges
28-58, 64-93, 96-115, 123-182, 208-228, 235-263, and 279-325 respectively.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/swingset/src/stories/scroll-area.mdx`:
- Around line 22-30: Resolve the unavailable scroll-area import used in the
example by either marking this API as internal/unreleased in the documentation
or exposing ./mosaic/components/scroll-area through the `@clerk/ui` package
exports map. Update the relevant documentation or package export configuration
while preserving the existing scrollAreaRoot and scrollAreaViewport usage.

In `@packages/swingset/src/stories/scroll-area.stories.tsx`:
- Around line 16-20: Update the exported StoryMeta configuration for the Scroll
Area story to set styleEngine to 'stylex', ensuring the documented
scrollAreaRoot and scrollAreaViewport APIs are rendered as StyleX APIs.

---

Nitpick comments:
In `@packages/swingset/src/stories/scroll-area.stories.tsx`:
- Around line 28-58: Add explicit React.JSX.Element return types to the
OrganizationRow, Default, NotScrollable, Gutter, HoverReveal, ThemedScrollbar,
and ShadowIndicators components in
packages/swingset/src/stories/scroll-area.stories.tsx at ranges 28-58, 64-93,
96-115, 123-182, 208-228, 235-263, and 279-325 respectively.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 8a14de27-07ab-43af-8ff4-8fa4f9b5b4b0

📥 Commits

Reviewing files that changed from the base of the PR and between 029359d and cef0fc7.

📒 Files selected for processing (16)
  • .changeset/gentle-hoops-relax.md
  • packages/swingset/CLAUDE.md
  • packages/swingset/src/components/ClientRoot.tsx
  • packages/swingset/src/components/DocsViewer.tsx
  • packages/swingset/src/components/app-sidebar.tsx
  • packages/swingset/src/lib/registry.ts
  • packages/swingset/src/stories/item.mdx
  • packages/swingset/src/stories/item.stories.tsx
  • packages/swingset/src/stories/scroll-area.mdx
  • packages/swingset/src/stories/scroll-area.stories.tsx
  • packages/ui/src/mosaic/components/scroll-area/index.ts
  • packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts
  • packages/ui/src/mosaic/components/scroll-area/scroll-area.test.ts
  • packages/ui/src/mosaic/components/scroll-area/scroll-area.vars.stylex.ts
  • packages/ui/src/mosaic/styles/index.ts
  • packages/ui/src/mosaic/tokens.stylex.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)
🚧 Files skipped from review as they are similar to previous changes (14)
  • packages/swingset/src/components/app-sidebar.tsx
  • packages/swingset/src/components/DocsViewer.tsx
  • packages/swingset/src/components/ClientRoot.tsx
  • packages/swingset/src/lib/registry.ts
  • packages/ui/src/mosaic/styles/index.ts
  • packages/swingset/src/stories/item.mdx
  • packages/swingset/CLAUDE.md
  • packages/swingset/src/stories/item.stories.tsx
  • packages/ui/src/mosaic/tokens.stylex.ts
  • packages/ui/src/mosaic/components/scroll-area/index.ts
  • packages/ui/src/mosaic/components/scroll-area/scroll-area.vars.stylex.ts
  • .changeset/gentle-hoops-relax.md
  • packages/ui/src/mosaic/components/scroll-area/scroll-area.test.ts
  • packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts

Comment on lines +22 to +30
```tsx
import { scrollAreaRoot, scrollAreaViewport } from '@clerk/ui/mosaic/components/scroll-area';
import * as stylex from '@stylexjs/stylex';

<div
{...stylex.props(scrollAreaRoot)}
style={{ height: 260 }}
>
<Item.Group {...stylex.props(...scrollAreaViewport())}>{organizations}</Item.Group>

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -HI '^package\.json$' packages/ui --max-depth 1 --exec cat {}
echo "---"
rg -n '"exports"' -A 40 packages/ui/package.json

Repository: clerk/javascript

Length of output: 7002


🏁 Script executed:

#!/bin/bash
set -e

printf '%s\n' '--- story context ---'
sed -n '1,75p' packages/swingset/src/stories/scroll-area.mdx

printf '%s\n' '--- mosaic package and build references ---'
rg -n --hidden -g '!node_modules' -g '!dist' \
  'mosaic/components/scroll-area|scrollAreaRoot|scrollAreaViewport|build:mosaic|dist-mosaic' \
  packages/ui packages/swingset pnpm-workspace.yaml package.json

printf '%s\n' '--- exact exports keys ---'
python3 - <<'PY'
import json
from pathlib import Path
p = Path("packages/ui/package.json")
data = json.loads(p.read_text())
exports = data.get("exports", {})
print("mosaic/components/scroll-area" in exports)
print([k for k in exports if "mosaic" in k])
PY

Repository: clerk/javascript

Length of output: 8752


Document the import as internal or expose the subpath.

@clerk/ui/package.json does not export ./mosaic/components/scroll-area. Consumers cannot resolve the import shown in this usage example. Mark the API as internal/unreleased, or add the subpath to the exports map.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/swingset/src/stories/scroll-area.mdx` around lines 22 - 30, Resolve
the unavailable scroll-area import used in the example by either marking this
API as internal/unreleased in the documentation or exposing
./mosaic/components/scroll-area through the `@clerk/ui` package exports map.
Update the relevant documentation or package export configuration while
preserving the existing scrollAreaRoot and scrollAreaViewport usage.

Comment on lines +16 to +20
export const meta: StoryMeta = {
group: 'Styles',
title: 'Scroll Area',
source: 'packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts',
};

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Set styleEngine to 'stylex'.

StoryMeta defaults to 'emotion'. This story documents scrollAreaRoot and scrollAreaViewport, which are StyleX APIs. The viewer can otherwise render Emotion-specific API rows.

Proposed fix
 export const meta: StoryMeta = {
   group: 'Styles',
   title: 'Scroll Area',
   source: 'packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts',
+  styleEngine: 'stylex',
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const meta: StoryMeta = {
group: 'Styles',
title: 'Scroll Area',
source: 'packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts',
};
export const meta: StoryMeta = {
group: 'Styles',
title: 'Scroll Area',
source: 'packages/ui/src/mosaic/components/scroll-area/scroll-area.styles.ts',
styleEngine: 'stylex',
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/swingset/src/stories/scroll-area.stories.tsx` around lines 16 - 20,
Update the exported StoryMeta configuration for the Scroll Area story to set
styleEngine to 'stylex', ensuring the documented scrollAreaRoot and
scrollAreaViewport APIs are rendered as StyleX APIs.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants