oxc-project/oxc · error · OxcDiagnostic

'{prop_name}' is a reserved attribute and cannot be used as

Error message

'{prop_name}' is a reserved attribute and cannot be used as props.

What it means

The vue/no-reserved-props rule reports props whose names collide with reserved template attributes. The source defines the sets per version: Vue 3 reserves `key` and `ref`; Vue 2 additionally reserves `is`, `slot`, `slot-scope`, `slotScope`, `class` and `style`. Declaring such a prop does not work — the template compiler consumes the attribute for its own purposes and never passes it as a prop. Which set applies is chosen by the rule's `vueVersion` option (deserialized via deserialize_vue_version); the rule checks both object-syntax components (is_vue_component_options_object) and `defineProps` type signatures, comparing kebab-cased names.

Source

Thrown at crates/oxc_linter/src/rules/vue/no_reserved_props.rs:32

use crate::{
    AstNode,
    context::LintContext,
    frameworks::FrameworkOptions,
    rule::{DefaultRuleConfig, Rule},
    utils::{
        for_each_define_props_type_signature, is_vue_component_options_object,
        vue_casing::kebab_case,
    },
};

/// Reserved attribute names that cannot be used as prop names, by Vue version.
const RESERVED_VUE3: &[&str] = &["key", "ref"];
const RESERVED_VUE2: &[&str] =
    &["key", "ref", "is", "slot", "slot-scope", "slotScope", "class", "style"];

fn no_reserved_props_diagnostic(prop_name: &str, span: Span) -> OxcDiagnostic {
    OxcDiagnostic::warn(format!(
        "'{prop_name}' is a reserved attribute and cannot be used as props."
    ))
    .with_label(span)
}

#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", default, deny_unknown_fields)]
struct NoReservedPropsConfig {
    /// Vue major version whose reserved attribute set is applied. Vue 2 reserves
    /// more names (`is`, `slot`, `class`, `style`, ...) than Vue 3.
    #[serde(deserialize_with = "deserialize_vue_version")]
    vue_version: u8,
}

impl Default for NoReservedPropsConfig {
    fn default() -> Self {
        Self { vue_version: 3 }
    }

View on GitHub (pinned to e1e7af627c)

Solutions

  1. Rename the prop to a non-reserved name (e.g. `itemKey`, `variant`, `cssClass`).
  2. If you need the native attribute behavior (`class`/`style`/`is`), rely on Vue's fallthrough attributes instead of declaring a prop.
  3. Verify the rule's `vueVersion` option matches your project (Vue 2 reserves more names than Vue 3).
  4. Re-run oxlint to confirm.

Example fix

// before
export default {
  props: ['key'] // reserved attribute
}

// after
export default {
  props: ['itemKey']
}
Defensive patterns

Strategy: validation

Validate before calling

// guard prop declarations at authoring time
const RESERVED = ['key', 'ref', 'is', 'slot', 'slot-scope', 'slotScope', 'class', 'style'];
function assertProps(props) {
  for (const name of Object.keys(props)) {
    if (RESERVED.includes(name)) throw new Error(`prop '${name}' is a reserved attribute`);
  }
}

Prevention

When it happens

Trigger: Declaring `props: ['key']`, `props: ['ref']`, or in Vue 2 mode `is`/`slot`/`class`/`style`, including camelCase forms like `slotScope` and `defineProps<{ is: boolean }>()` signatures once kebab-cased.

Common situations: Components modeling HTML concepts (`class` for CSS, `is` for dynamic types) ported into Vue props; migrating Vue 2 components to Vue 3 while linting with the wrong `vueVersion`; generic table/list components that accept a `key` prop.

Related errors


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