oxc-project/oxc · warning · OxcDiagnostic

Prefer `classList.toggle()` over `classList.add()` and `clas

Error message

Prefer `classList.toggle()` over `classList.add()` and `classList.remove()`

What it means

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).

Source

Thrown at crates/oxc_linter/src/rules/unicorn/prefer_classlist_toggle.rs:18

use oxc_ast::{
    AstKind,
    ast::{CallExpression, ChainElement, Expression, IfStatement, MemberExpression, Statement},
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::{GetSpan, Span};

use crate::{
    AstNode,
    context::LintContext,
    fixer::{RuleFix, RuleFixer},
    rule::Rule,
    utils::is_same_expression,
};

fn prefer_classlist_toggle_diagnostic(span: Span) -> OxcDiagnostic {
    OxcDiagnostic::warn(
        "Prefer `classList.toggle()` over `classList.add()` and `classList.remove()`",
    )
    .with_help("Use `classList.toggle()` instead")
    .with_label(span)
}

#[derive(Debug, Default, Clone)]
pub struct PreferClasslistToggle;

declare_oxc_lint!(
    /// ### What it does
    ///
    /// Prefers the use of `element.classList.toggle(className, condition)` over
    /// conditional add/remove patterns.
    ///
    /// ### Why is this bad?
    ///
    /// The `toggle()` method is more concise and expressive than using conditional

View on GitHub (pinned to e1e7af627c)

Solutions

  1. Replace the conditional with `el.classList.toggle('on')`, or `el.classList.toggle('on', force)` when you need explicit direction.
  2. Run `oxlint --fix`; the rule uses `is_same_expression` so only identical receivers/classes are rewritten — still glance at the diff.
  3. Keep the conditional and suppress inline (`// oxlint-disable-next-line unicorn/prefer-classlist-toggle`) if the branches differ (logging, counters).
  4. Disable the rule if the codebase standard uses explicit add/remove for clarity.

Example fix

// before
active ? btn.classList.add('on') : btn.classList.remove('on');

// after
btn.classList.toggle('on'); // or btn.classList.toggle('on', active)
Defensive patterns

Strategy: validation

Validate before calling

// oxlint --fix --filter unicorn/prefer-classlist-toggle src/
// CI gate: oxlint --deny-warnings src/

Prevention

When it happens

Trigger: `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.

Common situations: 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.

Related errors


AI-assisted analysis of oxc-project/oxc@e1e7af627c (2026-08-20). Data as JSON: /api/errors/ba78ec911ab16312. Report an issue: GitHub.