Skip to content

Content scrolls down by container's padding on load for pages without an autofocused input #14838

Description

@helloDanuk

Bug description

On every Control Panel page that (a) has no auto-focused input and (b) whose content overflows the viewport, the content area scrolls down by the top padding of .main-content (8px from sm:p-2`) right after load. It looks like a small "layout jump."

For example: This happens on the dashboard if it has a few widgets, or on an utilities page.

The cause is in the focus-management in resources/js/pages/layout/Layout.vue. focusMain() focuses the content wrapper as an accessibility convenience:

if (!autofocusInput) {
    document.querySelector('#content-card')?.focus();
}

#content-card is a full-height wrapper (min-h-full, tabindex="-1") at the very top of the scrollable #main-content container, which has sm:p-2 padding. Calling .focus() without { preventScroll: true } makes the browser scroll the overflowing container to bring the focused element into view, offsetting it by the 8px top padding.

This is why it only shows on input-less, overflowing pages (Dashboard, custom utility views). Listing pages (Collections, Assets, Users, …) are unaffected only as a side effect: their search field uses <Input :focus="true">, so an input is already focused when focusMain() runs and it returns early via the first branch — #content-card is never focused.

Bildschirmaufnahme.2026-06-18.um.16.27.54.mov

How to reproduce

  1. Open a CP page with no auto-focused input and enough content to overflow the viewport (e.g. Dashboard, or a custom Utility view).
  2. Make the browser window short enough that #main-content scrolls.
  3. Reload.

Expected: Content stays at scrollTop: 0.
Actual: Content scrolls down ~8px after load.

Quick console confirmation on such a page:

const mc = document.querySelector('.main-content');
mc.scrollTop = 0;
document.querySelector('#content-card').focus();
mc.scrollTop; // 8  (expected: 0)

Note: I could strangely reproduce this behaviour even on the empty state dashboard page from a fresh installation. But not always…

Suggested fix

Pass preventScroll when focusing the content wrapper in focusMain():

document.querySelector('#content-card')?.focus({ preventScroll: true });

Focus still moves to the content for keyboard/screen-reader users (the accessibility intent of focusMain() is preserved), but the browser no longer scrolls the container. The autofocus-input branch (autofocusInput.focus()) may want the same treatment for consistency.

A CSS-only workaround that confirms the diagnosis is .main-content { scroll-padding-top: 0.5rem; }, but { preventScroll: true } is the proper fix.

So far, this is what Claude has told me. I could fix it with for myself with this:

.main-content {
    scroll-padding-top: 0.5rem; /* = p-2 / 8px */
}

Logs

Environment

Environment
Laravel Version: 13.16.1
PHP Version: 8.4.19
Composer Version: 2.9.5
Environment: local
Debug Mode: ENABLED
Maintenance Mode: OFF
Timezone: UTC
Locale: en

Cache
Config: NOT CACHED
Events: NOT CACHED
Routes: NOT CACHED
Views: CACHED

Drivers
Broadcasting: log
Cache: file
Database: sqlite
Logs: stack / single
Mail: log
Queue: sync
Session: file

Storage
public/storage: NOT LINKED

Statamic
Addons: 0
License Key: Not set
Sites: 1
Stache Watcher: Enabled (auto)
Static Caching: Disabled
Version: 6.21.0 PRO

Installation

Fresh statamic/statamic site via CLI

Additional details

No response

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions