From 7b4fb211fdd5d64ba910592fc172da38e6f30c9c Mon Sep 17 00:00:00 2001 From: Tino Zijdel Date: Wed, 24 Dec 2025 11:42:27 +0100 Subject: [PATCH 1/3] Make tabindex configurable --- packages/core/src/Editor.ts | 4 +++- packages/core/src/extensions/tabindex.ts | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/core/src/Editor.ts b/packages/core/src/Editor.ts index 08cfb681e5..36fc74dd92 100644 --- a/packages/core/src/Editor.ts +++ b/packages/core/src/Editor.ts @@ -431,7 +431,9 @@ export class Editor extends EventEmitter { Commands, FocusEvents, Keymap, - Tabindex, + Tabindex.configure({ + value: this.options.coreExtensionOptions?.tabindex?.value, + }), Drop, Paste, Delete, diff --git a/packages/core/src/extensions/tabindex.ts b/packages/core/src/extensions/tabindex.ts index da14d02afe..4f03a4ba48 100644 --- a/packages/core/src/extensions/tabindex.ts +++ b/packages/core/src/extensions/tabindex.ts @@ -5,12 +5,18 @@ import { Extension } from '../Extension.js' export const Tabindex = Extension.create({ name: 'tabindex', + addOptions() { + return { + value: undefined, + } + }, + addProseMirrorPlugins() { return [ new Plugin({ key: new PluginKey('tabindex'), props: { - attributes: (): { [name: string]: string } => (this.editor.isEditable ? { tabindex: '0' } : {}), + attributes: (): { [name: string]: string } => (this.editor.isEditable ? { tabindex: this.options.value ?? '0' } : {}), }, }), ] From e64e5a73de7f8c579d3e50f970e14f2c49af3774 Mon Sep 17 00:00:00 2001 From: bdbch <6538827+bdbch@users.noreply.github.com> Date: Mon, 16 Feb 2026 13:55:43 +0100 Subject: [PATCH 2/3] Create many-houses-occur.md --- .changeset/many-houses-occur.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/many-houses-occur.md diff --git a/.changeset/many-houses-occur.md b/.changeset/many-houses-occur.md new file mode 100644 index 0000000000..b9da4b217c --- /dev/null +++ b/.changeset/many-houses-occur.md @@ -0,0 +1,5 @@ +--- +"@tiptap/core": minor +--- + +Add configurable tabindex option to allow customizing the tabindex attribute on the editor element via coreExtensionOptions From 206cdd7695b6a124c08da563e1259d106ff43747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnau=20G=C3=B3mez=20Farell?= Date: Thu, 7 May 2026 15:57:36 +0200 Subject: [PATCH 3/3] feat: add TabindexOptions type, docs, non-editable support and tests --- packages/core/__tests__/tabindex.spec.ts | 74 ++++++++++++++++++++++++ packages/core/src/extensions/tabindex.ts | 38 +++++++++++- packages/core/src/types.ts | 9 +++ 3 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 packages/core/__tests__/tabindex.spec.ts diff --git a/packages/core/__tests__/tabindex.spec.ts b/packages/core/__tests__/tabindex.spec.ts new file mode 100644 index 0000000000..39bfe674c9 --- /dev/null +++ b/packages/core/__tests__/tabindex.spec.ts @@ -0,0 +1,74 @@ +import { Editor } from '@tiptap/core' +import Document from '@tiptap/extension-document' +import Paragraph from '@tiptap/extension-paragraph' +import Text from '@tiptap/extension-text' +import { describe, expect, it } from 'vitest' + +describe('tabindex extension', () => { + it('should set tabindex="0" on editable editor by default', () => { + const editor = new Editor({ + extensions: [Document, Paragraph, Text], + }) + + expect(editor.view.dom.getAttribute('tabindex')).toBe('0') + + editor.destroy() + }) + + it('should not set tabindex on non-editable editor by default', () => { + const editor = new Editor({ + extensions: [Document, Paragraph, Text], + editable: false, + }) + + expect(editor.view.dom.getAttribute('tabindex')).toBeNull() + + editor.destroy() + }) + + it('should set custom tabindex on editable editor when configured', () => { + const editor = new Editor({ + extensions: [Document, Paragraph, Text], + coreExtensionOptions: { + tabindex: { + value: '4', + }, + }, + }) + + expect(editor.view.dom.getAttribute('tabindex')).toBe('4') + + editor.destroy() + }) + + it('should set custom tabindex on non-editable editor when configured', () => { + const editor = new Editor({ + extensions: [Document, Paragraph, Text], + editable: false, + coreExtensionOptions: { + tabindex: { + value: '-1', + }, + }, + }) + + expect(editor.view.dom.getAttribute('tabindex')).toBe('-1') + + editor.destroy() + }) + + it('should set tabindex="0" when value is explicitly undefined', () => { + const editor = new Editor({ + extensions: [Document, Paragraph, Text], + coreExtensionOptions: { + tabindex: { + value: undefined, + }, + }, + }) + + expect(editor.view.dom.getAttribute('tabindex')).toBe('0') + + editor.destroy() + }) +}) diff --git a/packages/core/src/extensions/tabindex.ts b/packages/core/src/extensions/tabindex.ts index 4f03a4ba48..aa886c8be2 100644 --- a/packages/core/src/extensions/tabindex.ts +++ b/packages/core/src/extensions/tabindex.ts @@ -2,7 +2,36 @@ import { Plugin, PluginKey } from '@tiptap/pm/state' import { Extension } from '../Extension.js' -export const Tabindex = Extension.create({ +/** + * Options for the Tabindex extension. + */ +export type TabindexOptions = { + /** + * The value for the `tabindex` attribute on the editor element. + * When undefined, editable editors default to `0` and non-editable editors get no tabindex. + */ + value?: string +} + +/** + * The Tabindex extension adds a configurable tabindex attribute to the editor. + * + * By default, the editor gets tabindex="0" when editable. This can be customized + * via coreExtensionOptions to support specific focus ordering requirements in forms + * or to enable focusing on non-editable editors. + * + * @example + * ```ts + * new Editor({ + * coreExtensionOptions: { + * tabindex: { + * value: '-1', + * }, + * }, + * }) + * ``` + */ +export const Tabindex = Extension.create({ name: 'tabindex', addOptions() { @@ -16,7 +45,12 @@ export const Tabindex = Extension.create({ new Plugin({ key: new PluginKey('tabindex'), props: { - attributes: (): { [name: string]: string } => (this.editor.isEditable ? { tabindex: this.options.value ?? '0' } : {}), + attributes: (): { [name: string]: string } => { + if (!this.editor.isEditable && this.options.value === undefined) { + return {} + } + return { tabindex: this.options.value ?? '0' } + }, }, }), ] diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index ad011f838f..85fb8a4b6f 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -339,6 +339,15 @@ export interface EditorOptions { clipboardTextSerializer?: { blockSeparator?: string } + /** + * Options for the `tabindex` core extension. + */ + tabindex?: { + /** + * The value for the `tabindex` attribute on the editor element. + */ + value?: string + } delete?: { /** * Whether the `delete` extension should be called asynchronously to avoid blocking the editor while processing deletions