Skip to the solution
MediumFrontend9 min read

What are fallthrough attributes ($attrs)?

393 views

The problem

Question presented to candidate: "Someone wrote <BaseButton class="primary" aria-label="Save" @click="save" />, but BaseButton only declares a label prop. Where do class, aria-label and the click listener end up, and what changes if the component has two root elements or sets inheritAttrs: false?"

What a strong answer should cover:

  • Fallthrough attributes are everything the parent passes that the child does not declare as a prop or an emitted event: class, style, id, aria-*, data-*, and v-on listeners (as onClick and so on).
  • With a single root element, Vue adds them to that root automatically; class and style are merged with the root's own, other attributes override.
  • Declared props and declared emits are removed from $attrs; undeclared listeners become native listeners on the root, which can make a handler fire twice if the child also emits that event.
  • With multiple roots (a fragment) Vue cannot choose, warns "Extraneous non-props attributes...", and you must bind v-bind="$attrs" on the right element.
  • inheritAttrs: false turns off the automatic behaviour so you can place $attrs on an inner element, such as the <input> inside a label wrapper.
  • If the root is another component, the attributes keep falling through to that component.

Clarifying questions expected:

  • "Does BaseButton emit its own click event, or is it relying on the native one?"
  • "Is the root element the right target, or should attributes reach an inner element?"

Code / implementation expected: Optional β€” a wrapper component with inheritAttrs: false and v-bind="$attrs" on the inner element.

  • attrs
  • fallthrough

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

9 min read Β· 9 sections

Read the code

VueSingle-root inheritance, the multi-root warning, and inheritAttrs: false with $attrs bound by hand
<script setup>
import { ref, defineComponent, h } from "vue";

// Single root: undeclared attributes land on the <button> automatically.
const BaseButton = defineComponent({
  props: { label: String },
  emits: ["confirm"],
  setup(props, { emit }) {
    return () => h("button", { class: "base", onClick: () => emit("confirm") }, props.label);
  },
});

// Two roots: Vue cannot guess where attributes go.
const TwoRoots = defineComponent({
  setup() {
    return () => [h("label", "Name"), h("input")];
  },
});

// inheritAttrs: false + bind $attrs on the inner element by hand.
const LabeledInput = defineComponent({
  inheritAttrs: false,
  props: { label: String },
  setup(props, { attrs }) {
    return () =>
      h("label", { class: "wrapper" }, [props.label + " ", h("input", { ...attrs })]);
  },
});

const clicks = ref(0);
const confirms = ref(0);
</script>

<template>
  <BaseButton
    label="Save"
    class="primary"
    style="color: red"
    data-test="save"
    aria-label="Save draft"
    @click="clicks++"
    @confirm="confirms++"
  />
  <TwoRoots class="oops" data-x="1" />
  <LabeledInput label="Email" class="field" type="email" placeholder="you@example.com" />
  <p>clicks {{ clicks }} confirms {{ confirms }}</p>
</template>
VueFieldRow.vue: a two-root SFC that binds $attrs on the input
<script setup>
// Two root nodes, so Vue cannot pick a target for fallthrough attributes.
// Binding $attrs on one of them tells Vue where they go (and silences the warning).
defineProps({ label: String });
</script>

<template>
  <label :for="$attrs.id">{{ label }}</label>
  <input v-bind="$attrs" />
</template>

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 58 of 92 in Vue.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Vue.js questions
Up nextMedium

What are composables in Vue?

Continue