{"record":{"id":"ba78ec911ab16312","repo":"oxc-project/oxc","slug":"prefer-classlist-toggle-over-classlist-add","errorCode":null,"errorMessage":"Prefer `classList.toggle()` over `classList.add()` and `classList.remove()`","messagePattern":"Prefer `classList\\.toggle\\(\\)` over `classList\\.add\\(\\)` and `classList\\.remove\\(\\)`","errorType":"validation","errorClass":"OxcDiagnostic","httpStatus":null,"severity":"warning","filePath":"crates/oxc_linter/src/rules/unicorn/prefer_classlist_toggle.rs","lineNumber":18,"sourceCode":"use oxc_ast::{\n    AstKind,\n    ast::{CallExpression, ChainElement, Expression, IfStatement, MemberExpression, Statement},\n};\nuse oxc_diagnostics::OxcDiagnostic;\nuse oxc_macros::declare_oxc_lint;\nuse oxc_span::{GetSpan, Span};\n\nuse crate::{\n    AstNode,\n    context::LintContext,\n    fixer::{RuleFix, RuleFixer},\n    rule::Rule,\n    utils::is_same_expression,\n};\n\nfn prefer_classlist_toggle_diagnostic(span: Span) -> OxcDiagnostic {\n    OxcDiagnostic::warn(\n        \"Prefer `classList.toggle()` over `classList.add()` and `classList.remove()`\",\n    )\n    .with_help(\"Use `classList.toggle()` instead\")\n    .with_label(span)\n}\n\n#[derive(Debug, Default, Clone)]\npub struct PreferClasslistToggle;\n\ndeclare_oxc_lint!(\n    /// ### What it does\n    ///\n    /// Prefers the use of `element.classList.toggle(className, condition)` over\n    /// conditional add/remove patterns.\n    ///\n    /// ### Why is this bad?\n    ///\n    /// The `toggle()` method is more concise and expressive than using conditional","sourceCodeStart":1,"sourceCodeEnd":36,"githubUrl":"https://github.com/oxc-project/oxc/blob/e1e7af627c8843ab64044ed466b128fcc21a035b/crates/oxc_linter/src/rules/unicorn/prefer_classlist_toggle.rs#L1-L36","documentation":"Lint diagnostic from oxlint's `unicorn/prefer-classlist-toggle` rule. The conditional pattern `el.classList.contains(x) ? el.classList.remove(x) : el.classList.add(x)` (or the `if/else` equivalent) is exactly what `classList.toggle(x)` does in one call — including the optional `force` second argument for explicit on/off. The rule flags that conditional and offers a fixer (guarded by `is_same_expression` so both branches really target the same element/class).","triggerScenarios":"`if (active) btn.classList.add('on'); else btn.classList.remove('on');`, ternary versions, and `toggle`-equivalent contains/add/remove chains on the same receiver expression. Detected during oxlint runs when the rule can match add/remove branches with identical target and class name.","commonSituations":"Theme switchers, selection state, hamburger buttons — anywhere UI state toggles a class. Very frequent in hand-written vanilla JS. When adopting oxlint's unicorn rules it appears in dozens of places at once. Careful with dynamic class names in branches (`add('a')` vs `remove('b')`) — those will not match, and false suppressions around similar code hide real bugs.","solutions":["Replace the conditional with `el.classList.toggle('on')`, or `el.classList.toggle('on', force)` when you need explicit direction.","Run `oxlint --fix`; the rule uses `is_same_expression` so only identical receivers/classes are rewritten — still glance at the diff.","Keep the conditional and suppress inline (`// oxlint-disable-next-line unicorn/prefer-classlist-toggle`) if the branches differ (logging, counters).","Disable the rule if the codebase standard uses explicit add/remove for clarity."],"exampleFix":"// before\nactive ? btn.classList.add('on') : btn.classList.remove('on');\n\n// after\nbtn.classList.toggle('on'); // or btn.classList.toggle('on', active)","handlingStrategy":"validation","validationCode":"// oxlint --fix --filter unicorn/prefer-classlist-toggle src/\n// CI gate: oxlint --deny-warnings src/","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use `classList.toggle(name)` for flip logic and `classList.toggle(name, force)` for explicit state.","Before suppressing, verify the two branches really do differ (extra side effects, different classes) — usually they don't."],"tags":["oxlint","unicorn","dom","css","classlist","autofix"],"backgroundTag":"lint-rule-violation","analyzedSha":"e1e7af627c8843ab64044ed466b128fcc21a035b","analyzedAt":"2026-08-20T07:01:07.079Z","contentChangedAt":"2026-08-20T07:01:07.079Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}