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

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions