Vize

active-element

Track the focused element of a document.

Package @vizejs/composable/active-element
Own the source vize lib pull composable:active-element
Runtime exports useActiveElement
Gzip budget 1280 B

Minimal setup

Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).

vp install @vizejs/composable

Import the entry directly. Call lifecycle-bound utilities inside <script setup> so cleanup follows the component scope; consult the runtime contract for this entry's SSR and hydration behavior.

import { useActiveElement } from "@vizejs/composable/active-element";

Try it

Show guidance for the focused profile field

Follow actual document focus to show contextual instructions for a small workshop profile form.

Open the interactive example

What to observe: Press Tab from Display name through Email address and Workshop note. The visible focused-field label and guidance follow each field. Focus display name moves focus back; Clear focus returns to the outside-field state.

useActiveElement returns a readonly shallow ref of the real focused Element, with null during SSR. It reads the document only after mounting and listens for capturing focus/blur events. The example recognizes only its own fields through useId-based owner markers. Other controls or a blurred field show the outside-field state. The default follows open shadow roots; this example uses native inputs. Document listeners are released with the component scope.

Compare the initial and interacted states
Show guidance for the focused profile field: initial state
Initial state
Show guidance for the focused profile field: state after the verified interactions
After the verified interactions

Copy the example

Save this complete SFC in your Vue project. Its public imports and source are the same ones executed in the preview.

<script setup lang="ts">
import { computed, useId, useTemplateRef } from "vue";
import { useActiveElement } from "@vizejs/composable/active-element";

const profileId = useId();
const nameInput = useTemplateRef<HTMLInputElement>("nameInput");
const active = useActiveElement();
const focusedField = computed(() =>
  active.value?.getAttribute("data-profile-owner") === profileId
    ? active.value.getAttribute("data-profile-field")
    : null,
);
const guidance = computed(() => {
  if (focusedField.value === "Display name") return "Use the name you want on your workshop badge.";
  if (focusedField.value === "Email address")
    return "Use an address where you can receive workshop details.";
  if (focusedField.value === "Workshop note") return "Write a note for the workshop organizer.";
  return "Focus a profile field to see its guidance.";
});

function focusName(): void {
  nameInput.value?.focus();
}

function clearFocus(): void {
  if (active.value instanceof HTMLElement) active.value.blur();
}
</script>

<template>
  <div class="composable-example">
    <label :for="`${profileId}-name`">Display name</label>
    <input
      :id="`${profileId}-name`"
      ref="nameInput"
      autocomplete="nickname"
      :data-profile-owner="profileId"
      data-profile-field="Display name"
      :aria-describedby="`${profileId}-guidance`"
    />
    <label :for="`${profileId}-email`">Email address</label>
    <input
      :id="`${profileId}-email`"
      type="email"
      autocomplete="email"
      :data-profile-owner="profileId"
      data-profile-field="Email address"
      :aria-describedby="`${profileId}-guidance`"
    />
    <label :for="`${profileId}-note`">Workshop note</label>
    <input
      :id="`${profileId}-note`"
      :data-profile-owner="profileId"
      data-profile-field="Workshop note"
      :aria-describedby="`${profileId}-guidance`"
    />
    <output aria-live="polite"
      >Focused field: {{ focusedField || "Outside the profile fields" }}</output
    >
    <p :id="`${profileId}-guidance`">{{ guidance }}</p>
    <div class="example-actions">
      <button type="button" @click="focusName">Focus display name</button>
      <button type="button" @click="clearFocus">Clear focus</button>
    </div>
    <p>Press Tab through the fields. The guidance follows the document's actual focused element.</p>
  </div>
</template>

Runtime contract

Utility Category Stability SSR Hydration Cleanup Targets Host globals Uses
useActiveElement dom experimental deterministic-fallback stable reactive-scope web, desktop document tryOnScopeDispose

API

useActiveElement

Track the focused element of a document. Listens for capturing focus/blur events on the document, so focus changes anywhere (including inside shadow trees) update the ref. Server renders expose null. Listeners are removed with the owning scope.

function useActiveElement( options: UseActiveElementOptions = {}, ): Readonly<ShallowRef<Element | null>>

Types

ActiveElementHost

Document-like capability observed by useActiveElement.

Member Type Description
activeElement Element | null Currently focused element.

UseActiveElementOptions

Options for useActiveElement.

Member Type Description
deep? boolean Descend into open shadow roots to report the innermost focused element.
host? MaybeRefOrGetter<ActiveElementHost | null | undefined> Reactive document capability for alternate runtimes and tests.