diff --git a/.server-changes/queues-page-table-filter-bar.md b/.server-changes/queues-page-table-filter-bar.md
new file mode 100644
index 00000000000..fddb7c14918
--- /dev/null
+++ b/.server-changes/queues-page-table-filter-bar.md
@@ -0,0 +1,6 @@
+---
+area: webapp
+type: improvement
+---
+
+On the Queues page, the queue search and pagination controls now sit directly above the queue table instead of in the top bar, so it is clear they filter the table and not the metrics above it
diff --git a/apps/webapp/app/components/layout/MetricsLayout.tsx b/apps/webapp/app/components/layout/MetricsLayout.tsx
index ccf5129b58a..0745e56202b 100644
--- a/apps/webapp/app/components/layout/MetricsLayout.tsx
+++ b/apps/webapp/app/components/layout/MetricsLayout.tsx
@@ -9,6 +9,7 @@
* - `Grid` — tiles; columns derived from tile count unless `columns` is set. `kind="charts"`
* bakes the fixed chart-row height.
* - `Content` — table / tabs below the tiles. Full-bleed by default; `inset` for a padded column.
+ * `toolbar` adds a bar flush above the content for controls that scope only this region.
*
* Optional:
* - `Sidebar` — a persistent right-hand panel; fixed `width` or `resizable`. Present ⇒ Root
@@ -22,12 +23,14 @@
* ```tsx
*
*
- * …search + TimeFilter…
- *
+ * …TimeFilter…
+ * …page-wide actions…
*
* …stat tiles…
* …chart tiles…
- * …table…
+ * …search…>}>
+ * …table…
+ *
*
* ```
*/
@@ -341,16 +344,44 @@ function MetricsLayoutGrid({
* spans edge to edge with its own top border; pass `inset` for a padded column (the detail page's
* tabs + charts). Separation from the tiles above comes from the scroll column's gap alone (no
* extra top margin), so the tile → content step matches the gap between tile rows.
+ *
+ * Pass `toolbar` for controls that scope this region only (search, pagination) — they sit flush on
+ * top of the content instead of in the page-wide `Filters` bar, so their scope is visible.
*/
function MetricsLayoutContent({
children,
inset = false,
+ toolbar,
}: {
children: ReactNode;
/** Pad the content into a column (page gutter) instead of letting it span edge to edge. */
inset?: boolean;
+ /**
+ * Controls that act on this region alone. Rendered as a bar directly above the content with no
+ * gap, so it reads as belonging to the table below rather than to the tiles above. Compose
+ * left/right clusters as child divs — `justify-between` spreads them.
+ */
+ toolbar?: ReactNode;
}) {
- return
{children}
;
+ const content = {children}
;
+
+ if (!toolbar) {
+ return content;
+ }
+
+ return (
+
+
+ {toolbar}
+
+ {content}
+
+ );
}
export const MetricsLayout = {
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx
index 4b5673c6f0e..05ac0e7b47d 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx
@@ -493,12 +493,10 @@ function QueuesWithMetricsView() {
- {/* Filters — pinned bar directly under the NavBar. Left cluster = search + period; right
- cluster = pagination. */}
+ {/* Filters — pinned bar directly under the NavBar. This row is page-wide only: Period is
+ the one control that changes the tiles and charts below, so it leads the row. Search
+ and pagination scope the table alone and live in that table's own bar instead. */}
-
-
-
+
+
{environment.runsEnabled &&
env.pauseSource !== ENVIRONMENT_PAUSE_SOURCE_BILLING_LIMIT ? (
) : null}
-
@@ -688,7 +682,22 @@ function QueuesWithMetricsView() {
) : null}
-
+
+
+
+ >
+ }
+ >
{/* Default overflow-x-auto container so wide tables still scroll horizontally on
narrow viewports; the page (not this region) owns vertical scrolling. */}