Skip to content

useScrollToError

A composable for scrolling to an element with validation error. Scrolls into view, optionally applies a highlight effect, focuses the first focusable control, and announces to screen readers.

Basic Usage

vue
<template>
  <form @submit.prevent="handleSubmit">
    <div data-field="tags">
      <input v-model="tags" />
    </div>
    <button type="submit">Submit</button>
  </form>
</template>

<script setup lang="ts">
import { useScrollToError } from "@baklavue/composables";

const { scrollToError } = useScrollToError();

const handleSubmit = () => {
  const err = validationError.value;
  if (err) {
    scrollToError(err);
  }
};
</script>

Direct Selector

vue
<script setup lang="ts">
import { useScrollToError } from "@baklavue/composables";

const { scrollToError } = useScrollToError();

// Scroll to element by selector
scrollToError('[data-field="tags"]');
</script>

With Validation Error Object

vue
<script setup lang="ts">
import { useScrollToError } from "@baklavue/composables";

const { scrollToError } = useScrollToError();

// Validation error with scrollTarget
const validationError = {
  field: "tags",
  message: "Please select at least one option",
  scrollTarget: '[data-field="tags"]',
};

scrollToError(validationError);
</script>

Methods

scrollToError

Scrolls to the target element and optionally applies highlight and focus.

typescript
scrollToError(target: ScrollToErrorTarget, options?: ScrollToErrorOptions): ScrollToErrorResult

Returns { success: boolean } indicating whether the target was found and scrolled to.

Target Types

Accepted by scrollToError:

  • CSS selector string – e.g. '[data-field="tags"]'
  • HTMLElement – direct element reference
  • Object with scrollTarget – e.g. { scrollTarget: '[data-field="tags"]' } for validation errors

Options

ScrollToErrorOptions

OptionTypeDefaultDescription
scrollBehavior'smooth' | 'auto' | 'instant''smooth'Scroll behavior
block'start' | 'center' | 'end' | 'nearest''center'Vertical scroll position
shineClassstring'error-shine'CSS class for highlight effect
shineDurationnumber2500Duration in ms to keep the shine class. Use 0 to disable
focusbooleantrueWhether to attempt focusing the first focusable element
focusDelaynumber300Delay in ms before focus attempt
scrollContainerstring | HTMLElement | nullScroll container for forms inside modals/drawers. When provided, scrolls this container instead of the viewport
announceboolean | stringtrueScreen reader announcement. true = default message, string = custom message, false = no announcement
scrollOffset{ top?: number; left?: number }Offset in pixels for fixed headers (e.g. { top: 80 })

UseScrollToErrorOptions (composable-level)

When creating the composable, you can pass default options applied to every call:

typescript
useScrollToError({
  defaultOptions: {
    scrollContainer: "[data-dialog-body]",
    shineClass: "my-app-error",
  },
});

Call-time options override these defaults.

Examples

With Custom Options

vue
<script setup lang="ts">
import { useScrollToError } from "@baklavue/composables";

const { scrollToError } = useScrollToError();

scrollToError('[data-field="tags"]', {
  shineClass: "my-shine",
  shineDuration: 1500,
  focusDelay: 500,
});
</script>

With Default Options (for modals)

vue
<script setup lang="ts">
import { useScrollToError } from "@baklavue/composables";

const { scrollToError } = useScrollToError({
  defaultOptions: {
    scrollContainer: "[data-dialog-body]",
    announce: "Form has errors. Please fix the highlighted field.",
  },
});

scrollToError('[data-field="tags"]');
</script>

Form in Modal with Scroll Container

vue
<template>
  <BvDialog v-model:open="isOpen">
    <div data-dialog-body class="dialog-body">
      <form @submit.prevent="handleSubmit">
        <div data-field="email">
          <input v-model="email" />
        </div>
      </form>
    </div>
  </BvDialog>
</template>

<script setup lang="ts">
import { useScrollToError } from "@baklavue/composables";

const { scrollToError } = useScrollToError({
  defaultOptions: { scrollContainer: "[data-dialog-body]" },
});

const handleSubmit = () => {
  if (hasError) {
    scrollToError('[data-field="email"]');
  }
};
</script>

With Fixed Header Offset

vue
<script setup lang="ts">
import { useScrollToError } from "@baklavue/composables";

const { scrollToError } = useScrollToError();

scrollToError('[data-field="tags"]', {
  scrollOffset: { top: 80 },
});
</script>

Without Shine Effect

vue
<script setup lang="ts">
import { useScrollToError } from "@baklavue/composables";

const { scrollToError } = useScrollToError();

scrollToError('[data-field="tags"]', {
  shineDuration: 0,
});
</script>

Without Focus

vue
<script setup lang="ts">
import { useScrollToError } from "@baklavue/composables";

const { scrollToError } = useScrollToError();

scrollToError('[data-field="tags"]', {
  focus: false,
});
</script>

Without Screen Reader Announcement

vue
<script setup lang="ts">
import { useScrollToError } from "@baklavue/composables";

const { scrollToError } = useScrollToError();

scrollToError('[data-field="tags"]', {
  announce: false,
});
</script>

Using Return Value

vue
<script setup lang="ts">
import { useScrollToError } from "@baklavue/composables";

const { scrollToError } = useScrollToError();

const handleSubmit = () => {
  const result = scrollToError('[data-field="tags"]');
  if (!result.success) {
    console.warn("Could not scroll to error field");
  }
};
</script>

CSS for Shine Effect

The composable uses error-shine by default. Define this class in your styles for the highlight effect:

css
.error-shine {
  animation: error-shine 2.5s ease-in-out;
}

@keyframes error-shine {
  0%,
  100% {
    box-shadow: none;
  }
  50% {
    box-shadow: 0 0 0 2px var(--bl-color-danger);
  }
}

TypeScript Support

typescript
import {
  useScrollToError,
  type ScrollToErrorOptions,
  type ScrollToErrorResult,
  type ScrollToErrorTarget,
  type UseScrollToErrorOptions,
} from "@baklavue/composables";

const { scrollToError } = useScrollToError();

// scrollToError accepts string, HTMLElement, or { scrollTarget: string }
const result = scrollToError('[data-field="tags"]');
// result.success: boolean

scrollToError(document.getElementById("field"));
scrollToError({ scrollTarget: '[data-field="tags"]' });