diff --git a/packages/core/src/components/layout/Layout.tsx b/packages/core/src/components/layout/Layout.tsx
index 4cc5bfeb3..780ca04e5 100644
--- a/packages/core/src/components/layout/Layout.tsx
+++ b/packages/core/src/components/layout/Layout.tsx
@@ -198,12 +198,22 @@ function LayoutRoot({
const hasAnyHeader = hasHeaderChild || hasLegacyHeader;
+ // `fill` bounds the page only while the columns sit side by side — always for
+ // one column, from `lg` for two, from `xl` for three or more (the breakpoints
+ // the grid template above uses). Below that the columns stack and the page
+ // flows and scrolls as usual; bounding a stacked grid would squash every
+ // column but the last to zero height.
+ let fillFrom: "always" | "lg" | "xl" = "always";
+ if (gridTemplate && effectiveColumnCount === 2) fillFrom = "lg";
+ if (gridTemplate && effectiveColumnCount >= 3) fillFrom = "xl";
+
return (
[data-layout-column]]:min-h-0",
- ],
+ fill && "astw:flex-1",
+ fill &&
+ fillFrom === "always" && [
+ "astw:min-h-0 astw:[&>[data-layout-column]]:min-h-0",
+ hasAnyHeader ? "astw:grid-rows-[auto_minmax(0,1fr)]" : "astw:grid-rows-[minmax(0,1fr)]",
+ ],
+ fill &&
+ fillFrom === "lg" && [
+ "astw:lg:min-h-0 astw:lg:[&>[data-layout-column]]:min-h-0",
+ hasAnyHeader
+ ? "astw:lg:grid-rows-[auto_minmax(0,1fr)]"
+ : "astw:lg:grid-rows-[minmax(0,1fr)]",
+ ],
+ fill &&
+ fillFrom === "xl" && [
+ "astw:xl:min-h-0 astw:xl:[&>[data-layout-column]]:min-h-0",
+ hasAnyHeader
+ ? "astw:xl:grid-rows-[auto_minmax(0,1fr)]"
+ : "astw:xl:grid-rows-[minmax(0,1fr)]",
+ ],
className,
)}
style={
diff --git a/packages/core/src/components/sidebar/content-container.tsx b/packages/core/src/components/sidebar/content-container.tsx
index 81c8f4399..ac965e329 100644
--- a/packages/core/src/components/sidebar/content-container.tsx
+++ b/packages/core/src/components/sidebar/content-container.tsx
@@ -82,7 +82,11 @@ export const ContentContainer = ({ header, className, children }: ContentContain
// A page bounds everything internally, so this area
// never needs to scroll: clip instead. This also makes the fade
// inert (no scroll range) and avoids any sub-pixel-overflow bar.
- "astw:has-data-[layout-fill]:overflow-hidden",
+ // A multi-column fill page is only bounded once its columns sit
+ // side by side; while they stack, this area scrolls as usual.
+ "astw:has-data-[layout-fill=always]:overflow-hidden",
+ "astw:lg:has-data-[layout-fill=lg]:overflow-hidden",
+ "astw:xl:has-data-[layout-fill=xl]:overflow-hidden",
// Reserve the scrollbar gutter so content doesn't shift when the
// bar toggles across navigations (no-op with overlay scrollbars).
"astw:[scrollbar-gutter:stable]",
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 9a4cde02d..4aa3b5eb1 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -209,6 +209,13 @@ export type { PositionProps } from "./lib/position";
export { Sheet } from "./components/sheet";
export { Tabs } from "./components/tabs";
export { Toolbar, type ToolbarProps } from "./components/toolbar";
+export {
+ FilterRail,
+ useFilterRailCounts,
+ useFilterRailLayout,
+ type FilterRailRootProps,
+ type FilterRailSection,
+} from "./components/filter-rail";
export { Tooltip } from "./components/tooltip";
export { Select, type SelectAsyncFetcher } from "./components/select";
export { Combobox, type ComboboxAsyncFetcher } from "./components/combobox";