diff --git a/packages/graphiql-console/src/components/ApiVersionSelector/ApiVersionSelector.test.tsx b/packages/graphiql-console/src/components/ApiVersionSelector/ApiVersionSelector.test.tsx
new file mode 100644
index 0000000000..ab91be3abd
--- /dev/null
+++ b/packages/graphiql-console/src/components/ApiVersionSelector/ApiVersionSelector.test.tsx
@@ -0,0 +1,94 @@
+import {ApiVersionSelector} from './ApiVersionSelector.tsx'
+import React from 'react'
+import {render, screen, fireEvent} from '@testing-library/react'
+import {describe, test, expect, vi} from 'vitest'
+import {AppProvider} from '@shopify/polaris'
+
+// Helper to wrap components in AppProvider
+function renderWithProvider(element: React.ReactElement) {
+ return render({element})
+}
+
+describe('', () => {
+ const defaultVersions = ['2024-01', '2024-04', '2024-07', '2024-10', 'unstable']
+
+ test('renders Select component', () => {
+ const onChange = vi.fn()
+ renderWithProvider()
+
+ // Select should be rendered with the accessibility label
+ const select = screen.getByLabelText('API version')
+ expect(select).toBeDefined()
+ })
+
+ test('Select has hidden label', () => {
+ const onChange = vi.fn()
+ renderWithProvider()
+
+ // Label should exist but be visually hidden (Polaris labelHidden behavior)
+ const select = screen.getByLabelText('API version')
+ expect(select).toBeDefined()
+ })
+
+ test('renders all version options', () => {
+ const onChange = vi.fn()
+ renderWithProvider()
+
+ const select = screen.getByLabelText('API version')
+
+ expect(select.options).toHaveLength(5)
+ expect(select.options[0].value).toBe('2024-01')
+ expect(select.options[1].value).toBe('2024-04')
+ expect(select.options[2].value).toBe('2024-07')
+ expect(select.options[3].value).toBe('2024-10')
+ expect(select.options[4].value).toBe('unstable')
+ })
+
+ test('displays currently selected value', () => {
+ const onChange = vi.fn()
+ renderWithProvider()
+
+ const select = screen.getByLabelText('API version')
+ expect(select.value).toBe('2024-07')
+ })
+
+ test('calls onChange when selection changes', () => {
+ const onChange = vi.fn()
+ renderWithProvider()
+
+ const select = screen.getByLabelText('API version')
+ fireEvent.change(select, {target: {value: '2024-04'}})
+
+ expect(onChange).toHaveBeenCalledTimes(1)
+ // Polaris Select may pass additional parameters, check first argument
+ expect(onChange.mock.calls[0][0]).toBe('2024-04')
+ })
+
+ test('handles empty versions array', () => {
+ const onChange = vi.fn()
+ renderWithProvider()
+
+ const select = screen.getByLabelText('API version')
+ expect(select.options).toHaveLength(0)
+ })
+
+ test('handles single version', () => {
+ const onChange = vi.fn()
+ renderWithProvider()
+
+ const select = screen.getByLabelText('API version')
+ expect(select.options).toHaveLength(1)
+ expect(select.options[0].value).toBe('2024-10')
+ })
+
+ test('handles custom version strings', () => {
+ const customVersions = ['v1', 'v2', 'v3-beta']
+ const onChange = vi.fn()
+ renderWithProvider()
+
+ const select = screen.getByLabelText('API version')
+ expect(select.options[0].value).toBe('v1')
+ expect(select.options[1].value).toBe('v2')
+ expect(select.options[2].value).toBe('v3-beta')
+ })
+})
diff --git a/packages/graphiql-console/src/components/ApiVersionSelector/ApiVersionSelector.tsx b/packages/graphiql-console/src/components/ApiVersionSelector/ApiVersionSelector.tsx
new file mode 100644
index 0000000000..eab699e510
--- /dev/null
+++ b/packages/graphiql-console/src/components/ApiVersionSelector/ApiVersionSelector.tsx
@@ -0,0 +1,27 @@
+import React from 'react'
+import {Select} from '@shopify/polaris'
+
+interface ApiVersionSelectorProps {
+ // Available API versions
+ versions: string[]
+ // Currently selected version
+ value: string
+ // Callback when version changes
+ onChange: (version: string) => void
+}
+
+/**
+ * API version selector component
+ * Replaces vanilla JS event listener and manual DOM manipulation
+ */
+export function ApiVersionSelector({versions, value, onChange}: ApiVersionSelectorProps) {
+ return (
+