diff --git a/apps/studio/.claude/skills/explorer/SKILL.md b/apps/studio/.claude/skills/explorer/SKILL.md
index 3a62f2dfe19..3a086025fe8 100644
--- a/apps/studio/.claude/skills/explorer/SKILL.md
+++ b/apps/studio/.claude/skills/explorer/SKILL.md
@@ -40,3 +40,41 @@ Compose the toolbar from slots rather than adding resource-specific props:
- Use `ExplorerToolbarAction` for compact direct actions. Icon-only actions are 28px wide automatically.
- Keep execution, persistence, source selection, and other resource state in the consuming Explorer surface.
- Extend layouts with children and `className`; avoid boolean props for resource-specific variants.
+
+## Explorer query shell
+
+Import the layout regions from:
+
+```tsx
+import {
+ ExplorerQuery,
+ ExplorerQueryEditor,
+ ExplorerQueryFooter,
+ ExplorerQueryResults,
+ ExplorerQueryViewport,
+} from '@/components/interfaces/Explorer/ExplorerQuery'
+```
+
+Use `ExplorerQuery` for a framed query embedded in a notebook, chat, or another surface. Give it an explicit height when the surrounding surface constrains the cell:
+
+```tsx
+
+ {/* title and actions */}
+ {/* editable or read-only SQL */}
+ {/* idle, loading, error, or result display */}
+ {/* row count or surface metadata */}
+
+```
+
+Use `ExplorerQueryViewport` when a query owns the content area of an Explorer tab. Its parent must provide a bounded height and `min-h-0`:
+
+```tsx
+
+ {/* the same query composition */}
+
+```
+
+- `ExplorerQueryResults` is always present and fills the space left by the toolbar, editor, and footer.
+- A result renderer that can grow supplies its own `min-h-0 flex-1 overflow-auto` container.
+- The shell owns layout only. Query models, source resolution, execution, results, display selection, and saved configuration stay controlled by the consumer.
+- Compose approval prompts, confirmation notices, and other surface-specific content as children between the standard regions.
diff --git a/apps/studio/components/interfaces/Explorer/ExplorerQuery/ExplorerQuery.test.tsx b/apps/studio/components/interfaces/Explorer/ExplorerQuery/ExplorerQuery.test.tsx
new file mode 100644
index 00000000000..f01c68a66e9
--- /dev/null
+++ b/apps/studio/components/interfaces/Explorer/ExplorerQuery/ExplorerQuery.test.tsx
@@ -0,0 +1,91 @@
+import { render, screen } from '@testing-library/react'
+import { createRef } from 'react'
+import { describe, expect, it } from 'vitest'
+
+import { ExplorerToolbar, ExplorerToolbarTitle } from '../ExplorerToolbar'
+import {
+ ExplorerQuery,
+ ExplorerQueryEditor,
+ ExplorerQueryFooter,
+ ExplorerQueryResults,
+ ExplorerQueryViewport,
+} from './index'
+
+describe('ExplorerQuery', () => {
+ it('composes the toolbar, editor, results, and footer without owning their behavior', () => {
+ render(
+
+
+ Weekly signups
+
+ SQL editor
+ Results table
+ 2 rows
+
+ )
+
+ const cell = screen.getByLabelText('Weekly signups query')
+ expect(cell).toHaveAttribute('data-slot', 'explorer-query')
+ expect(cell).toHaveAttribute('data-variant', 'embedded')
+ expect(cell).toHaveClass('rounded-md', 'border')
+ expect(screen.getByRole('toolbar', { name: 'Explorer query toolbar' })).toBeInTheDocument()
+ expect(cell.querySelector('[data-slot="explorer-query-editor"]')).toHaveTextContent(
+ 'SQL editor'
+ )
+ expect(cell.querySelector('[data-slot="explorer-query-results"]')).toHaveTextContent(
+ 'Results table'
+ )
+ expect(cell.querySelector('[data-slot="explorer-query-footer"]')).toHaveTextContent('2 rows')
+ })
+
+ it('provides an explicit full-height viewport variant and forwards native props', () => {
+ const viewportRef = createRef()
+
+ render(
+
+ SQL editor
+ Run the query to see results
+
+ )
+
+ const viewport = screen.getByLabelText('Explorer query tab')
+ expect(viewport).toHaveAttribute('data-variant', 'viewport')
+ expect(viewport).toHaveAttribute('data-density', 'compact')
+ expect(viewport).toHaveClass('h-full', 'min-h-0', 'custom-viewport')
+ expect(viewport).not.toHaveClass('rounded-md')
+ expect(viewportRef.current).toBe(viewport)
+ })
+
+ it('fills the remaining height from the results region itself, in either root and at any depth', () => {
+ render(
+ <>
+
+ Results table
+
+
+
+ Results table
+
+
+ >
+ )
+
+ // The fill behavior belongs to the region, so it survives both roots and a
+ // wrapper between them — it is not granted by a parent selector.
+ const [embedded, wrapped] = Array.from(
+ document.querySelectorAll('[data-slot="explorer-query-results"]')
+ )
+
+ // min-h-0 over a floor: results must shrink rather than push the footer out
+ // of a height-constrained frame.
+ for (const results of [embedded, wrapped]) {
+ expect(results).toHaveClass('flex-1', 'min-h-0', 'overflow-hidden')
+ }
+ expect(wrapped.parentElement).toHaveAttribute('data-testid', 'wrapper')
+ })
+})
diff --git a/apps/studio/components/interfaces/Explorer/ExplorerQuery/index.tsx b/apps/studio/components/interfaces/Explorer/ExplorerQuery/index.tsx
new file mode 100644
index 00000000000..c37de6e65b5
--- /dev/null
+++ b/apps/studio/components/interfaces/Explorer/ExplorerQuery/index.tsx
@@ -0,0 +1,87 @@
+import React from 'react'
+import { cn } from 'ui'
+
+const explorerQueryClassName = 'flex flex-col overflow-hidden bg-muted'
+
+export type ExplorerQueryProps = React.ComponentProps<'div'>
+
+/**
+ * Framed Explorer query shell for notebooks and other embedded surfaces.
+ * The consuming surface owns the height; results fill whatever the toolbar,
+ * editor, and footer leave behind. The minimum height keeps the frame usable
+ * when a surface leaves the height to content.
+ */
+const ExplorerQuery = ({ className, ...props }: ExplorerQueryProps) => (
+
+)
+ExplorerQuery.displayName = 'ExplorerQuery'
+
+export type ExplorerQueryViewportProps = React.ComponentProps<'div'>
+
+/**
+ * Full-height Explorer query shell for a dedicated tab or another viewport.
+ * Fills its container so the surrounding page decides the height.
+ */
+const ExplorerQueryViewport = ({ className, ...props }: ExplorerQueryViewportProps) => (
+
+)
+ExplorerQueryViewport.displayName = 'ExplorerQueryViewport'
+
+export type ExplorerQueryEditorProps = React.ComponentProps<'div'>
+
+/** Region containing the editable or read-only SQL editor supplied by the caller. */
+const ExplorerQueryEditor = ({ className, ...props }: ExplorerQueryEditorProps) => (
+
+)
+ExplorerQueryEditor.displayName = 'ExplorerQueryEditor'
+
+export type ExplorerQueryResultsProps = React.ComponentProps<'div'>
+
+/**
+ * Always-present result region for idle, loading, error, table, or chart content.
+ * Fills the height left by the toolbar, editor, and footer, and shrinks rather
+ * than pushing them out of the frame. Content that can overflow supplies its own
+ * scroll container.
+ */
+const ExplorerQueryResults = ({ className, ...props }: ExplorerQueryResultsProps) => (
+
+)
+ExplorerQueryResults.displayName = 'ExplorerQueryResults'
+
+export type ExplorerQueryFooterProps = React.ComponentProps<'div'>
+
+/** Optional metadata or surface-specific content below the results region. */
+const ExplorerQueryFooter = ({ className, ...props }: ExplorerQueryFooterProps) => (
+
+)
+ExplorerQueryFooter.displayName = 'ExplorerQueryFooter'
+
+export {
+ ExplorerQuery,
+ ExplorerQueryEditor,
+ ExplorerQueryFooter,
+ ExplorerQueryResults,
+ ExplorerQueryViewport,
+}