Skip to content

fix(BottomSheet): resting content starts below the grab bar, whatever the padding - #8558

Draft
cixzhang wants to merge 6 commits into
mainfrom
lane4/bottom-sheet-clears-grab-bar
Draft

cixzhang wants to merge 6 commits into
mainfrom
lane4/bottom-sheet-clears-grab-bar

Conversation

@cixzhang

@cixzhang cixzhang commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

User impact

A person opening a bottom sheet on a phone saw the top of its first line drawn through the grab bar. The bar floats over the content (24 px, opaque behind the pill, fading out below it). At the default padding the first line started inside the fade, and in a Popover's sheet (12 px padding) it started under the opaque part, so the line's top was cut.

Now, at rest, the content starts below the bar whatever the sheet's padding. Scrolled content still passes beneath the bar.

Problem and solution fit

  • The Content area's block-start inset is max(padding, --spacing-6), the bar's height. The band under the bar isn't container padding: the sheet publishes only the padding past it (max(padding - --spacing-6, 0)) and applies the band on top. A lone Section or Layout that escapes its padding therefore still starts below the bar. Every other edge applies and publishes the same inset. It's the content box's own padding, so it scrolls away with the content.
  • Core's, Lab's and the CLI page templates' own sheets reserved the band themselves. The CLI's table-filter and table-inbox templates drop theirs, so the band isn't doubled. Lab's MobileTokenizer keeps its 32 px start, and MobilePowerSearch's sticky header sits over the band at rest and clears the bar once stuck.
  • Core's own padding={0} sheets: The touch date, date-time and time pickers drop their 24 px top padding, so they look the same. The DropdownMenu and ContextMenu sheet content drops its 16 px top, so the first row sits 8 px lower, below the bar.
  • component:BottomSheet's Content area inset and compatibility line record the rule; the doc and padding JSDoc describe it. Changeset: [feat] (spec:AST-017 FR7), a visible layout change.
  • Cost: a default sheet's content starts 8 px lower (12 px for a Popover sheet), so a hug-height sheet is 8 px taller. A detent-sized sheet shows 8 px less content at that detent.

Credit: vjeux's #8514 found and measured the problem and proposed shortening the fade instead. This change takes the other direction its body lists (reserving the band), and its Chromium spec is adapted from #8514's measurements (co-authored).

Before and after

Chromium, 390×844 at 3×, the sheet's top 120 px. Left: before. Right: after.

Before After
Popover sheet, at rest Popover sheet at rest, before: the first line's top is cut by the bar Popover sheet at rest, after: the first line starts below the bar
Popover sheet, scrolled 37 px Popover sheet scrolled, before Popover sheet scrolled, after: content passes under the bar
Default BottomSheet, at rest Default sheet at rest, before: the first line starts in the fade Default sheet at rest, after: the first line starts below the bar
Default BottomSheet, scrolled 37 px Default sheet scrolled, before Default sheet scrolled, after: content passes under the bar

A sheet whose only child is a Section (Showcase, Tall Sheet, Hug Height and Text Only in Storybook). The Section escapes the sheet's padding but not the band:

Before After
Lone-Section sheet before: the first row starts 13 px below the bar's top, under the fade Lone-Section sheet after: the first row starts 37 px below the bar's top, below the bar

Core's own padding={0} sheets, which no longer reserve the band themselves:

Before After
DropdownMenu sheet DropdownMenu sheet before: the heading starts 17 px below the bar's top, inside the fade DropdownMenu sheet after: the heading starts 25 px below the bar's top, below the bar
TimeInput picker sheet Time picker sheet before Time picker sheet after: unchanged, the same 348 px sheet

Measured (first line's top, from the bar's top; the bar is 24 px):

Before After
Default sheet, at rest 17 px (inside the fade) 25 px
Popover sheet, at rest 13 px (under the opaque part) 25 px
Either, scrolled 37 px above the bar's top above the bar's top (passes under)
DropdownMenu sheet, first row 17 px (inside the fade) 25 px (sheet 8 px taller)
Lone-Section sheet, first row 13 px (under the fade) 37 px (the band plus the Section's own 12 px)
TimeInput picker sheet sheet 348 px tall unchanged, 348 px

Evidence

  • jsdom: BottomSheetPanel.test.tsx's padding tests pin the applied block-start inset to max(<published padding>, --spacing-6) for the default chain, padding={0} and padding={2}. All three fail on main.
  • Chromium: new BottomSheetGrabBar.a11y.chromium.spec.ts. At rest the first line starts below the whole bar (showcase, tall-sheet, text-only). Sheets whose content fits still don't scroll. Scrolled content passes under the bar.
  • BottomSheet, DateInput, DateTimeInput, TimeInput, DropdownMenu and ContextMenu jsdom suites pass (1106 plus the date-time suite).

… the padding

The grab bar floats over the content: 24 px tall, opaque behind the pill
and fading out across its lower half. At the default padding (16 px), a
sheet's first line started inside that fade, and a Popover sheet's (12 px)
under its opaque part, so the top of the first line read washed out or cut.

At rest, the content box's block-start inset is now never less than the
bar's height (`max(padding, --spacing-6)`), so the first line starts below
the bar for any `padding`, `padding={0}` included. The inset is the content
box's own padding, so it scrolls away with the content, which still passes
beneath the bar. The published inset stays the padding, so a child that
bleeds into it also starts below the bar. component:BottomSheet's Content
area inset records the rule.

Chromium proof: BottomSheetGrabBar.a11y.chromium.spec.ts (first line below
the bar at rest; fitting sheets don't scroll; scrolled content passes
under the bar), adapted from the measurements in the alternative fix that
shortened the fade instead.

Co-authored-by: Christopher Chedeau <vjeuxx@gmail.com>
@vercel

vercel Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
astryx Ready Ready Preview Oct 27, 2026 5:21am UTC

Request Review

@astracat-bot

astracat-bot Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🟡 Reviewed (for maintainers only) Oct 27, 2026, 6:22 AM UTC

@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Oct 26, 2026
@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR

🧪 Sandbox Preview

View Sandbox for this PR

Modified Components

ContextMenu (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 1.1KB 1.1KB 0B
Lines of Code 678 678 -
Complexity Very High Very High (86) -
DateInput (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 266B 266B 0B
Lines of Code 3532 3532 -
Complexity Very High Very High (390) -
DateTimeInput (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 286B 286B 0B
Lines of Code 3609 3609 -
Complexity Very High Very High (559) -
TimeInput (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 411B 411B 0B
Lines of Code 1245 1245 -
Complexity Very High Very High (194) -
MobilePowerSearch (@astryxdesign/lab) · View in Storybook
Metric Before After Delta
Entry File (ESM) 336B 336B 0B
Lines of Code 2492 2493 -
Complexity Very High Very High (317) -
MobileTokenizer (@astryxdesign/lab) · View in Storybook
Metric Before After Delta
Entry File (ESM) 328B 328B 0B
Lines of Code 596 596 -
Complexity Very High Very High (64) -

Component Size

Component Import before Import after Delta CSS before CSS after Delta
core/AlertDialog 56.9KB 56.9KB +28B 12.4KB 12.5KB +13B
core/BottomSheet 34.0KB 34.1KB +156B 7.9KB 8.0KB +162B
core/BottomSheetSwitcher 14.8KB 14.8KB 0B 3.3KB 3.3KB 0B
core/Card 6.4KB 6.5KB +35B 6.3KB 6.3KB +19B
core/ClickableCard 12.8KB 12.8KB +37B 6.9KB 6.9KB +18B
core/CommandPalette 50.9KB 50.9KB +33B 11.8KB 11.8KB +14B
core/ContextMenu 77.7KB 77.6KB -145B 14.6KB 14.8KB +149B
core/ContextMenuItem 16.6KB 16.6KB 0B 5.2KB 5.2KB 0B
core/DateInput 99.9KB 99.9KB +23B 14.8KB 15.0KB +182B
core/DateTimeInput 110.1KB 110.1KB +29B 15.2KB 15.4KB +173B
core/Dialog 16.2KB 16.2KB +27B 7.1KB 7.1KB +17B
core/Drawer 31.6KB 31.7KB +41B 8.0KB 8.1KB +22B
core/DrawerHeader 48.9KB 49.0KB +29B 11.2KB 11.2KB +23B
core/DropdownMenu 72.7KB 72.5KB -142B 14.7KB 14.9KB +186B
core/DropdownMenuCheckboxItem 16.1KB 16.1KB 0B 5.6KB 5.6KB 0B
core/DropdownMenuDivider 6.6KB 6.6KB 0B 3.6KB 3.6KB 0B
core/DropdownMenuGroup 6.1KB 6.1KB 0B 3.3KB 3.3KB 0B
core/DropdownMenuItem 15.4KB 15.4KB 0B 5.2KB 5.2KB 0B
core/DropdownMenuRadioGroup 6.0KB 6.0KB 0B 3.2KB 3.2KB 0B
core/DropdownMenuRadioItem 16.2KB 16.2KB 0B 5.6KB 5.6KB 0B
core/DropdownMenuSubMenu 58.1KB 58.1KB 0B 9.5KB 9.5KB 0B
core/Layout 8.1KB 8.1KB 0B 5.5KB 5.5KB 0B
core/LayoutContent 7.4KB 7.4KB 0B 4.9KB 4.9KB 0B
core/LayoutFooter 7.3KB 7.3KB 0B 4.8KB 4.8KB 0B
core/LayoutHeader 7.4KB 7.4KB 0B 4.8KB 4.8KB 0B
core/LayoutPanel 7.6KB 7.6KB 0B 4.8KB 4.8KB 0B
core/Markdown 144.5KB 144.3KB -164B 17.4KB 17.5KB +132B
core/MoreMenu 72.9KB 72.8KB -134B 14.7KB 14.9KB +186B
core/MultiSelector 76.2KB 76.2KB +33B 14.6KB 14.8KB +161B
core/Pagination 87.7KB 87.8KB +37B 15.3KB 15.5KB +146B
core/Popover 49.8KB 49.7KB -140B 12.1KB 12.3KB +172B
core/PowerSearch 155.1KB 155.2KB +196B 18.6KB 18.8KB +155B
core/Section 7.5KB 7.5KB +33B 6.2KB 6.2KB +18B
core/SelectableCard 12.8KB 12.8KB +29B 7.1KB 7.1KB +20B
core/Selector 79.1KB 79.2KB +40B 15.0KB 15.1KB +146B
core/Table 84.0KB 83.8KB -139B 15.1KB 15.3KB +128B
core/TableCell 78.8KB 78.7KB -135B 14.8KB 15.0KB +147B
core/TableHeaderCell 78.6KB 78.5KB -134B 14.8KB 15.0KB +147B
core/TableSelectionToolbar 52.4KB 52.4KB +35B 11.2KB 11.2KB +21B
core/TimeInput 86.8KB 86.9KB +33B 13.8KB 13.9KB +168B
core/Toolbar 39.8KB 39.8KB +30B 7.7KB 7.7KB +20B
lab/MobilePowerSearch 159.7KB 159.9KB +191B 1.4KB 1.4KB +18B
lab/MobileTokenizer 91.2KB 91.4KB +218B 775B 773B -2B

Sizes are gzip. Import counts the JavaScript the import loads eagerly; CSS counts the package stylesheet rules referenced by the modules it can load from its own package, lazily loaded ones included.

Bundle Size Summary

Gzip figures measure each package's entry file, not the code an import pulls in.

Package Entry (ESM) Entry gzip before Entry gzip after Delta
@astryxdesign/core 4.9KB 1.2KB 1.2KB 0B
@astryxdesign/lab 4.6KB 1.9KB 1.9KB 0B
Package stylesheet Gzip before Gzip after Delta
@astryxdesign/core 34.6KB 34.7KB +115B
@astryxdesign/lab 11.1KB 11.1KB -1B

Accessibility Audit

Status: No accessibility violations detected.

Visual Regression

57 of 502 shot(s) changed.

A change here is a question, not a failure: check whether the after is the
picture you intended. Record that review on the PR. Baseline maintenance is an
explicit dispatch of CI; this report never rewrites the baseline or adds a release gate.

component story theme mode pixels
Selector Indicator Space Bottom Sheet Narrow End RTL neutral light 749,301
Selector Indicator Space Bottom Sheet Wide Start neutral light 730,849
RichTextEditor Markdown parity: side by side probe dark 558,840
RichTextEditor Markdown parity: side by side probe light 540,756
Chat Full AI Chat probe light 230,823
Chat Full AI Chat probe dark 229,805
AppShell With Mobile Nav probe light 217,010
AppShell With Mobile Nav probe dark 216,960
Collapsible Dividers probe dark 66,445
Collapsible Dividers probe light 66,445
AppShell Playground neutral dark 65,198
AppShell Playground neutral light 63,788
Chat Default neutral dark 36,304
Chat Default neutral light 35,767
BottomSheet Text Only neutral dark 32,600
BottomSheet Text Only neutral light 32,502
Chat Empty State probe light 23,531
Chat Empty State probe dark 22,622
ChatAutoScroll Scroll Behavior Comparison neutral dark 15,378
ChatAutoScroll Scroll Behavior Comparison neutral light 14,107

and 37 more.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is waiting on an engineering owner to decide the new BottomSheet rule it adds: at rest, a sheet's block-start inset is never less than the grab bar's 24px. That rule replaces the current inset (the padding prop, then theme padding, then --spacing-4) and the unpadded padding={0} contract, so an engineering owner records the decision by commenting /approve-spec <full head SHA> here; the author may do so as an engineering owner. Whatever the decision, a few fixes are needed:

  • The DateInput, DateTimeInput and TimeInput touch pickers and the DropdownMenu and ContextMenu action sheets pass padding={0} and already reserve the band, so they now get a second 24px strip under the handle and grow 24px taller.
  • --container-padding-block-start now publishes less than the padding actually applied, but FR4 and the container-padding architecture require the two to match: publish the applied value or amend those records in this PR.
  • BottomSheet.doc.mjs, the padding JSDoc and the record's compatibility line still describe the old inset, and under AST-017 FR3 this change is a [feat], not a [fix].

[Automated review]

// Content area inset). The inset is the content box's own padding, so it
// scrolls away with the content, which then passes under the bar.
clearsGrabBar: {
paddingBlockStart: `max(var(--container-padding-block-start, 0px), ${HANDLE_BAR_HEIGHT})`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This floor also applies to padding={0} sheets that already reserve the band themselves: TouchDateField, TouchDateTimeField and TouchTimeField pad --spacing-6 in their sheet bodies, and MenuBottomSheet (DropdownMenu, ContextMenu) pads its menu content. In Chromium their first row moves down 24px and each hug sheet grows 24px. Either drop those callers' own band in this PR or leave padding={0} unfloored. Separately, --container-padding-block-start stays below the value applied here, while FR4 and the container-padding architecture require the published inset to match the applied one.

Comment on lines +198 to +200
on `bottom-sheet`, then `--spacing-4` on every logical edge. At rest the
block-start inset is never less than the grab bar's height (`--spacing-6`),
so the first row starts below the bar; scrolled content still passes under it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds a normative floor, but the record still says padding={0} keeps an unpadded Content area (Compatibility and migration, lines 63-65), and FR4 still says the Content area publishes the applied inset. Please reconcile those in this PR. An engineering owner decides the floor itself (/approve-spec <full head SHA>).

'@astryxdesign/core': patch
---

[fix] BottomSheet: at rest, a sheet's content starts below the grab bar whatever its `padding`, so the first line is no longer drawn through the bar's fade. Scrolled content still passes beneath the bar. A sheet at the default padding starts its content 8 px lower; one with `padding={0}` keeps its content clear of the bar.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AST-017 FR3 uses [feat] for a layout change that intentionally changes visual direction and [fix] for one that restores current visual authority. No current claim reserves the band, so this reads as [feat], still a patch. It would also help to name the old padding={0} appearance (edge-to-edge) next to the new 24px inset.

…heets stop reserving the band

From CodeBunny's review at a6642d3:
- The block-start inset is now floored where it is published, so the
  Content area applies and publishes the same value (bleed children escape
  exactly what they sit in): the theme default's chain in
  Layout/container.stylex.ts, and each explicit padding step in
  BottomSheetPanel.
- Core's own padding={0} sheets reserved the band under the grab bar
  themselves, so they grew a second 24px strip. The touch date, date-time
  and time pickers drop their 24px top padding (they look the same), and
  the DropdownMenu and ContextMenu sheet content drops its 16px top (their
  first row sits 8px lower, below the bar).
- BottomSheet's doc, `padding` JSDoc and the record's compatibility line
  describe the floor; the changeset is a [feat] (spec:AST-017 FR7).
@github-actions github-actions Bot added the needs:design-review Affects visuals — Design should review label Oct 26, 2026
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 26, 2026

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is waiting on an engineering owner to decide the new BottomSheet rule it writes into the component record: at rest the Content area's block-start inset is never less than the grab bar's 24px, replacing the current --spacing-4 inset and the unpadded padding={0} contract. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Whatever the decision, a few fixes are needed:

  • Because the floored inset is also published, a lone Section escapes the whole band, so the Showcase, Tall Sheet, Hug Height and Text Only sheets render exactly as before, with the first line 12-17px under the bar, and the new Chromium spec's at-rest tests fail for them.
  • Lab's MobilePowerSearch and MobileTokenizer sheets pass padding={0} with their own top band and now get a second 24px strip.
  • docs/architecture/container-padding.md and Popover's DEC-8 still state the unfloored inset, and BottomSheet.doc.mjs (line 98), the Container Padding story and a few comments still describe the old float.

[Automated review]

},
containerPaddingBlockStart: {
'--container-padding-block-start': bottomSheetBlockStart,
'--container-padding-block-start': bottomSheetBlockStartClear,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Publishing the floored value keeps FR4's published-equals-applied rule, but it is also what a first-child Section subtracts (Section.tsx:69-72): a lone Section escapes all 24px and pads only its own 16px, so Showcase, Tall Sheet and Hug Height still start their first line 16-17px below the bar's top in Chromium, pixel-identical to main (Text Only: 12px). Keeping the band out of what bleed children escape, for example by reserving it on the scrolling body, would let the content box still apply and publish one value.

description: JSON.stringify(reading),
});
expect(reading.firstLineTop).not.toBeNull();
expect(reading.firstLineTop!).toBeGreaterThanOrEqual(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this head this assertion cannot pass for showcase, tall-sheet or text-only: each puts a lone Section first, which escapes the whole published inset, and a Chromium probe at 390x844 reads the first line 17px, 16px and 12px below the bar's top against a 24px fadeEnd.

Comment on lines +202 to +206
on `bottom-sheet`, then `--spacing-4` on every logical edge. At rest the
block-start inset is never less than the grab bar's height (`--spacing-6`),
so the first row starts below the bar; scrolled content still passes under it.
The Content area applies and publishes the same inset, so a child that bleeds
into it escapes exactly what it sits in.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These sentences disagree for bleed content: a lone Section 'escapes exactly what it sits in', all 24px, so its first row does not start below the bar. docs/architecture/container-padding.md (lines 117-122) and Popover's DEC-8 ('inset by Popover's padding') also restate the old inset and need the same amendment in this PR.

type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
description:
'Internal padding of the sheet content using the spacing scale, matching Dialog. When omitted, uses the theme default for bottom sheets (--spacing-4 unless the theme sets padding on bottom-sheet). A lone Section child escapes the padding, and bleed children such as Table and Divider compensate against it. Use 0 for edge-to-edge content that supplies its own inset.',
"Internal padding of the sheet content using the spacing scale, matching Dialog. When omitted, uses the theme default for bottom sheets (--spacing-4 unless the theme sets padding on bottom-sheet). A lone Section child escapes the padding, and bleed children such as Table and Divider compensate against it. Use 0 for edge-to-edge content that supplies its own inset. Whatever the padding, the block-start inset is never less than the grab bar's height (--spacing-6), so resting content starts below the bar.",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The component description at line 98 still says 'content starts at the theme's bottom-sheet padding (--spacing-4 by default)', and the Container Padding story still says 'padding={0} restores the unpadded content box' (apps/storybook/stories/BottomSheet.stories.tsx:130); both need the floor too.

// content, which then passes under the bar.
const clearsGrabBarStyles = stylex.create({
0: {
'--container-padding-block-start': `max(${spacingVars['--spacing-0']}, ${HANDLE_BAR_HEIGHT})`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

padding={0} now gets a 24px top inset, so callers that still pad their own band double it: lab's MobilePowerSearch header (paddingBlockStart: --spacing-6, MobilePowerSearch.tsx:323-324, three sheets) and MobileTokenizer's sheet body (--spacing-8, MobileTokenizer.tsx:160). In Chromium the Filters heading moves from 33px to 57px below the bar's top.

…ection escapes

From CodeBunny's review at c65c74a: publishing the floored inset let a
lone Section (Showcase, Tall Sheet, Hug Height, Text Only) escape the whole
band, so its first line still started 12-17px under the bar.

The band under the bar is no longer container padding. The Content area
publishes only the block-start padding past it
(`max(padding - --spacing-6, 0)`) and applies the band on top
(`calc(published + --spacing-6)`), so its applied inset is still
max(padding, the bar) and what a lone Section or Layout escapes ends at the
band: a lone Section's first line now starts 37px below the bar's top
(13px before). Every other edge applies and publishes the same inset.

- Lab's MobileTokenizer and MobilePowerSearch sheets (`padding={0}`) no
  longer double the band: the tokenizer's body keeps its 32px start, and the
  search sheets' sticky header sits over the band at rest, clearing the bar
  only once stuck.
- component:BottomSheet's inset and compatibility lines,
  architecture:container-padding, Popover's DEC-8 wording, BottomSheet's
  doc, the Container Padding story and comments describe the band.
- The Chromium spec's at-rest check now includes Hug Height.
@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 812b064

@github-actions github-actions Bot removed the needs:spec-owner-review Current knowledge records await owner approval label Oct 26, 2026

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The grab-bar rule is approved for this head and the core change works as described, but a few fixes are needed before it can merge. Please address these; the new head then needs a fresh /approve-spec from an engineering owner:

  • The table-filter and table-inbox page templates in packages/cli still add their own 24px top band inside their bottom sheets (SHEET_TOP_INSET and the composer's paddingBlockStart={3}), so they now open with a second empty band; drop it as Core and Lab do here, and name the CLI update in the changeset.
  • component:BottomSheet FR4 and its verification row still say the published inset equals the applied padding and that a lone padded Section keeps its geometry, so they need the same band exception as the new Content area inset.
  • The changeset says default-padding content moves 8 px, but a sheet whose only child is a Section moves 24 px and grows by the same amount, and the changeset should say so.

[Automated review]

on `bottom-sheet`, then `--spacing-4` on every logical edge. At rest the
block-start inset is never less than the grab bar's height (`--spacing-6`),
so the first row starts below the bar; scrolled content still passes under it.
That band under the bar is not container padding: the Content area publishes

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This carve-out needs FR4 to match. FR4 (line 154) still says the Content area 'publishes the applied inset', and its verification row (line 385) fails when 'The published inset differs from the applied padding, or a lone padded Section changes geometry'. This head now does both by design. Could you amend FR4 and its row to name the band here as well?

'@astryxdesign/core': patch
---

[feat] BottomSheet: at rest, a sheet's content starts below the grab bar whatever its `padding`, so the first line is no longer drawn through the bar's fade. The block-start inset is never less than the bar's height (`--spacing-6`): the band under the bar is not padding a child escapes, so a lone `Section` starts below the bar too; scrolled content still passes beneath it. A sheet at the default padding starts its content 8 px lower, and one with `padding={0}` starts it below the bar. Core's own `padding={0}` sheets (the touch date and time pickers, DropdownMenu and ContextMenu sheets) no longer reserve that band themselves, so the pickers look the same and a menu's first row sits 8 px lower.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 8 px holds for content placed directly in the sheet. A sheet whose only child is a Section or Layout (Showcase, Tall Sheet, Hug Height, Text Only) moves 24 px, and its hug height grows by the same amount. The CLI's table-filter and table-inbox page templates also need their own top band removed, which makes this a @astryxdesign/cli update to classify here too.

] as const;
// The block-start inset never drops below the grab bar's height, so
// resting content starts below the bar (component:BottomSheet, Content
// area inset); it is applied and published alike.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the block start this no longer holds: the tests below expect max(calc(… - var(--spacing-6)),0px) to be published and calc(var(--container-padding-block-start,0px)+var(--spacing-6)) to be applied. The same stale claim is in the content-box comment at BottomSheetPanel.tsx:304-305.

… FR4 states the band exception

From CodeBunny's review at 812b064:
- The CLI's table-filter and table-inbox page templates reserved their own
  24px band inside their sheets (`SHEET_TOP_INSET`, the composer's
  `paddingBlockStart={3}`); the sheet keeps that band now, so they drop it
  and table-filter's sheets open on the same line as before.
- component:BottomSheet FR4 and its verification row state the block-start
  exception: the Content area publishes only the padding past the band, so
  a child escapes the padding but never the band.
- The changeset says a sheet whose only child is a Section moves that
  Section 24px and grows by the same amount, and names the CLI update.
@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 1c54a3a

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks: the grab-bar rule is approved for this head and works as described, with resting content now starting below the bar and scrolled content still passing beneath it. One record fix is needed before merge: the INV2 verification row in docs/architecture/container-padding.md still fails the Content area when it publishes a different block-start inset than it applies, which this change now does by design, so it needs the same band exception you added to FR4's row. Any new commit also needs a fresh /approve-spec <full head SHA> from an engineering owner, which you can record yourself.

[Automated review]

Comment on lines +123 to +128
container lowering. At the block start a sheet keeps a band under its floating
grab bar (`--spacing-6`) that is not container padding: it publishes only the
padding past that band and applies the band on top, so its applied block-start
inset is `max(padding, --spacing-6)` and a lone Section or Layout that escapes
the published padding still starts below the bar (`component:BottomSheet`,
Content area inset).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This paragraph now says the block start publishes only the padding past the band, but the INV2 verification row further down (line 376) still fails BottomSheet when "The Content area publishes an inset that differs from its applied padding". Could it get the same band exception FR4's row now has, e.g. "…on an edge other than the block start, its block start publishes more than the padding past the grab bar's band, or…"?

] as const;
// The block-start inset never drops below the grab bar's height, so
// resting content starts below the bar (component:BottomSheet, Content
// area inset); it is applied and published alike.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: at the block start the published and applied values now differ by design (the assertions below check exactly that), so "it is applied and published alike" no longer holds. The content-box comment at BottomSheetPanel.tsx:304-305 has the same stale wording.

…w state the block-start band exception

From CodeBunny's review at 1c54a3a: architecture:container-padding's
host-side rule and its INV2 verification row for BottomSheetPanel still
required the published block-start inset to equal the applied one; both now
name the exception the Content area inset decides (the block start publishes
only the padding past the grab bar's band).
@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Oct 27, 2026

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is waiting on an engineering owner to approve the BottomSheet grab-bar rule as this head writes it: the Content area's block-start inset is never less than the bar's 24px and only the padding past that band is published, which replaces the current inset in component:BottomSheet and the published-equals-applied rule in architecture:container-padding. The earlier /approve-spec comments named earlier commits, so an engineering owner records the decision for this head by commenting /approve-spec <full head SHA> on the pull request, and the author may do so as an engineering owner. Two optional touch-ups before that: the comments at BottomSheetPanel.test.tsx:417-419 and BottomSheetPanel.tsx:304-305 still say the block start is applied and published alike, and INV11 in container-padding.md still calls any published/applied mismatch a conformance gap.

[Automated review]

currently available at each logical edge through the existing private
`--container-padding-*` variables. Those values must match that state's
applied padding and follow nesting and overlay reset; missing values mean zero.
applied padding, except BottomSheet's block start, which publishes only the

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this exception the host-side rule treats BottomSheet's block start as an intended split, but INV11 (line 289) still says a host state "whose published inset does not match its applied padding is ineligible until that conformance gap is fixed". Nothing compensates at a sheet's block start today, so behavior is unaffected; naming the exception in INV11 too, or saying the block start stays ineligible by design, would keep the record consistent for whoever approves it.

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 18aee67

@github-actions github-actions Bot removed the needs:spec-owner-review Current knowledge records await owner approval label Oct 27, 2026
@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 1eb521c

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes are needed before merge. The grab-bar rule has an engineering owner's approval for this head and works as described, but merging main replaced the FR4 row in packages/core/src/BottomSheet/BottomSheet.spec.md (line 157) with a copy of FR4's verification row, so the record no longer states FR4's invariant. Please restore that row with the band-exception wording it had before the merge, then record a fresh /approve-spec <full head SHA> for the new commit, which you can do yourself as an engineering owner.

[Automated review]

| FR2 | The Sheet panel carries `bottom-sheet`; Content area, Handle, and Scrim carry no BottomSheet public target. | Current source, docs, and tests |
| FR3 | Content renders in one real observed content box. Effective overflow, containment, and keyboard access come from `useScrollableArea`; the named overflowing body delegates forward Tab entry to a safe first sequential native link/button. | `spec:AST-025`; tests |
| FR4 | The Content area pads caller content by the sheet's container inset and publishes the applied inset to descendants as a container publisher. | `architecture:container-padding` |
| FR4 | `BottomSheetPanel.test.tsx` container padding tests; `derivedVarRegistry.test.ts` | default, `padding` prop, theme `padding`, lone padded Section child | The published inset differs from the applied padding on an edge other than the block start, the block start publishes more than the padding past the grab bar's band, or a lone padded Section escapes into the band. | `audit:BottomSheet/anatomy` |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This row now holds the five cells of FR4's verification row (line 387) under the three-column ID | Invariant | Basis header, so FR4's invariant is gone. It looks like the merge of main kept the wrong line here. Here is the wording from before the merge:

| FR4  | The Content area pads caller content by the sheet's container inset and publishes the applied inset to descendants as a container publisher, except at the block start: there it publishes only the padding past the band it keeps under the grab bar (Content area inset), so a child escapes the padding but never the band. | `architecture:container-padding` |

@cixzhang
cixzhang marked this pull request as draft October 31, 2026 18:08

This branch was successfully deployed

1 active deployment
Preview — 1eb521c8 Deployed Oct 27, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot. needs:design-review Affects visuals — Design should review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant