{"record":{"id":"7cae4c8585fafa65","repo":"oxc-project/oxc","slug":"tabindex-should-only-be-declared-on-interactive","errorCode":null,"errorMessage":"`tabIndex` should only be declared on interactive elements.","messagePattern":"`tabIndex` should only be declared on interactive elements\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/oxc_linter/src/rules/jsx_a11y/no_noninteractive_tabindex.rs","lineNumber":24,"sourceCode":"use oxc_macros::declare_oxc_lint;\nuse oxc_span::Span;\nuse oxc_str::CompactStr;\nuse schemars::JsonSchema;\nuse serde::Deserialize;\n\nuse crate::{\n    AstNode,\n    context::LintContext,\n    globals::HTML_TAG,\n    rule::{DefaultRuleConfig, Rule},\n    utils::{\n        get_element_type, has_jsx_prop_ignore_case, is_interactive_element, is_interactive_role,\n        parse_jsx_value,\n    },\n};\n\nfn no_noninteractive_tabindex_diagnostic(span: Span) -> OxcDiagnostic {\n    OxcDiagnostic::warn(\"`tabIndex` should only be declared on interactive elements.\")\n        .with_help(\"The `tabIndex` attribute should be removed.\")\n        .with_label(span)\n}\n\n#[derive(Debug, Default, Clone, Deserialize)]\npub struct NoNoninteractiveTabindex(Box<NoNoninteractiveTabindexConfig>);\n\n#[derive(Debug, Clone, Deserialize, JsonSchema)]\n#[serde(rename_all = \"camelCase\", default, deny_unknown_fields)]\nstruct NoNoninteractiveTabindexConfig {\n    /// An array of custom HTML elements that should be considered interactive.\n    tags: Vec<CompactStr>,\n    /// An array of ARIA roles that should be considered interactive.\n    roles: Vec<CompactStr>,\n    /// If `true`, allows tabIndex values to be expression values (e.g., variables, ternaries). If `false`, only string literal values are allowed.\n    allow_expression_values: bool,\n}\n","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/oxc-project/oxc/blob/e1e7af627c8843ab64044ed466b128fcc21a035b/crates/oxc_linter/src/rules/jsx_a11y/no_noninteractive_tabindex.rs#L6-L42","documentation":"This is the `jsx_a11y/no-noninteractive-tabindex` rule in oxlint. It fires when a `tabIndex` prop (value parsed via `parse_jsx_value`) is declared on a non-interactive element — one that is neither natively interactive (`is_interactive_element`), carrying an interactive role (`is_interactive_role`), nor in the configured custom `tags` list. Adding non-interactive elements to the tab order creates confusing stops with no operable behavior.","triggerScenarios":"A JSX element with a `tabIndex` attribute whose element type is a non-interactive HTML tag (e.g. `div`, `span`, `li`) or a custom component not listed in the rule's `tags` config, without an interactive role.","commonSituations":"`<div tabIndex={0}>` used to make cards or rows clickable; focusable spans wrapping icons; UI kit components that forward `tabIndex` to wrappers; teams enabling jsx-a11y after years of div-based interactive components.","solutions":["Replace the element with a native interactive one (`button`, `a href`) so `tabIndex` is unnecessary.","Remove the `tabIndex` prop if the element is purely presentational.","If it is genuinely focusable-and-operable via ARIA, add the matching interactive `role` and keyboard handlers.","For custom elements implemented as interactive, add their tag names to the rule's `tags` config."],"exampleFix":"// before\n<div tabIndex={0} onClick={open}>Open card</div>\n\n// after\n<button type=\"button\" onClick={open} className=\"card\">Open card</button>","handlingStrategy":"validation","validationCode":"npx oxlint --jsx-a11y/no-noninteractive-tabindex src/","typeGuard":"const isInteractiveHost = (props: Record<string, unknown>) =>\n  ['button', 'a', 'input', 'select', 'textarea'].includes(String(props.as ?? 'div')) ||\n  typeof props.role === 'string' && ['button', 'link', 'checkbox'].includes(props.role);","tryCatchPattern":null,"preventionTips":["Only set tabIndex on elements that are also operable via keyboard.","Register your custom interactive component names in the rule's tags config.","Prefer tabIndex={0} plus role plus key handlers, or a native element."],"tags":["jsx-a11y","react","accessibility","tabindex","keyboard-navigation","lint"],"backgroundTag":"noninteractive-tabindex","analyzedSha":"e1e7af627c8843ab64044ed466b128fcc21a035b","analyzedAt":"2026-08-20T07:01:07.079Z","contentChangedAt":"2026-08-20T07:01:07.079Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}