Skip to content

[#308] Fixed sticky header offset when site alerts are present. - #309

Merged
AlexSkrypnyk merged 2 commits into
developfrom
feature/308-sticky-hdr-alerts
Aug 12, 2026
Merged

[#308] Fixed sticky header offset when site alerts are present.#309
AlexSkrypnyk merged 2 commits into
developfrom
feature/308-sticky-hdr-alerts

Conversation

@AlexSkrypnyk

@AlexSkrypnyk AlexSkrypnyk commented Aug 12, 2026

Copy link
Copy Markdown
Member

Closes #308

Checklist before requesting a review

  • Subject includes ticket number as [#123] Verb in past tense.
  • Ticket number #123 added to description
  • Added context in Changed section
  • Self-reviewed code and commented in commented complex areas.
  • Added tests for fix/feature.
  • Relevant tests run and passed locally.

Changed

  1. Replaced .ct-header--sticky's position: fixed (with no top, so it painted at its static position and never moved on scroll) with a zero-height position: sticky rail, .ct-page__header--sticky, wrapped around the header in page.twig.
  2. Because the rail has no height it reserves no space in the page flow, so content still starts at the top of the page and slides under the header exactly as before; because it is sticky, it starts at the alerts' bottom edge and pins at the top of the window once the page scrolls past them, for an alert of any height added or dismissed at any time.
  3. Pinned the rail at top: var(--drupal-displace-offset-top, 0) so it sits below core Navigation's administration top bar (published via Drupal.displace()) instead of underneath it.
  4. Moved the header's z-index and its :has([data-flyout-expanded]) mobile-flyout lift onto the rail, since position: sticky always creates its own stacking context; .ct-header--sticky now only carries its transparency and backdrop-blur treatment.
  5. Retargeted admin_navigation.feature's two stacking assertions from .ct-header--sticky to .ct-page__header--sticky, where the z-index now lives.
  6. Added tests/behat/features/sticky_header.feature, which publishes a site-wide alert, loads the homepage, confirms the alert renders ahead of the header, scrolls to the footer, and asserts the header is flush with the top of the window.
  7. Backed the new scenario with a the element :selector should be pinned to the top of the viewport step in FeatureContext.php, since the existing behat-steps "at the top of the viewport" step accepts any offset up to the viewport height and passes on the broken build.

Verified in the browser at desktop and mobile widths, with and without an alert, as an anonymous visitor and as an administrator, and with the mobile navigation flyout open. The new scenario was checked both ways: it fails against the old CSS with Expected element ".ct-header" to be pinned to the top of the viewport, but it is 266px from it. and passes against the fix.

Screenshots

Sticky header pinned flush to the top of the window on a scrolled homepage

Before / After

Before: .ct-header--sticky is position: fixed with no top (defaults to auto)

.ct-page
├── .ct-alert
└── .ct-header--sticky

.ct-alert is injected by alert.js, ahead of the header.
.ct-header--sticky: position: fixed; top: auto -> paints at its
own static position, the slot it would occupy in normal flow,
which the alert's height pushes down. That offset is fixed at
layout time and never moves again, even once the page scrolls
past the alert (measured 188px on the homepage at 1265px wide).

  scrollY 0        scrollY 600
  ┌──────────┐     ┌──────────┐
  │ alert    │     │ content  │  <- content scrolls above
  ├──────────┤     ├──────────┤
  │ HEADER   │     │ HEADER   │  <- stuck at 188px, always
  ├──────────┤     ├──────────┤
  │ content  │     │ content  │  <- and behind
  └──────────┘     └──────────┘

After: positioning moves to a zero-height sticky rail around the header

.ct-page
├── .ct-alert
└── .ct-page__header--sticky
    └── .ct-header

.ct-page__header--sticky: position: sticky; height: 0;
top: var(--drupal-displace-offset-top, 0). height: 0 reserves
no space, so content still starts at the top of the page and
slides under the header exactly as before. Because it is
position: sticky, it starts at the alert's bottom edge and pins
at `top` once the page scrolls past it - correct for an alert of
any height, added or dismissed at any time. z-index and the
flyout :has(...) lift moved here too, since position: sticky
always creates its own stacking context; .ct-header--sticky now
only carries its transparency/backdrop-blur treatment.

  scrollY 0        scrollY 100      scrollY 600
  ┌──────────┐     ┌──────────┐     ┌──────────┐
  │ alert    │     │ alert    │     │ HEADER   │  <- pinned at 0
  ├──────────┤     ├──────────┤     ├──────────┤
  │ HEADER   │     │ HEADER   │     │ content  │
  ├──────────┤     ├──────────┤     │          │
  │ content  │     │ content  │     │          │
  └──────────┘     └──────────┘     └──────────┘
     188px            88px             0px

@AlexSkrypnyk AlexSkrypnyk added the A1 Board worker 1 label Aug 12, 2026
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1160f031-7f4c-4604-a7c3-461c04c78975

📥 Commits

Reviewing files that changed from the base of the PR and between 6382ee8 and e601d82.

📒 Files selected for processing (6)
  • tests/behat/bootstrap/FeatureContext.php
  • tests/behat/features/admin_navigation.feature
  • tests/behat/features/sticky_header.feature
  • web/themes/custom/drevops/components/03-organisms/header/header.scss
  • web/themes/custom/drevops/components/04-templates/page/page.scss
  • web/themes/custom/drevops/components/04-templates/page/page.twig
💤 Files with no reviewable changes (1)
  • web/themes/custom/drevops/components/03-organisms/header/header.scss

📝 Walkthrough

Walkthrough

The page now renders sticky headers through a page-level rail. Styles account for administration chrome and mobile navigation. Behat coverage verifies alert ordering, scrolling behavior, viewport pinning, and stacking selectors.

Changes

Sticky header behavior

Layer / File(s) Summary
Header rail rendering and positioning
web/themes/custom/drevops/components/04-templates/page/page.twig, web/themes/custom/drevops/components/04-templates/page/page.scss, web/themes/custom/drevops/components/03-organisms/header/header.scss
The page template adds a conditional sticky-header wrapper. Page styles position the rail and adjust stacking. The header removes its previous sticky positioning rules.
Sticky header behavior tests
tests/behat/bootstrap/FeatureContext.php, tests/behat/features/sticky_header.feature, tests/behat/features/admin_navigation.feature
Behat adds a viewport-top assertion and tests alert ordering, footer scrolling, and updated stacking selectors.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant FeatureContext
  participant Browser
  participant page.twig
  participant page.scss
  FeatureContext->>Browser: Navigate to the homepage and scroll to the footer
  page.twig->>Browser: Render the sticky header wrapper
  Browser->>page.scss: Apply rail and stacking styles
  FeatureContext->>Browser: Read the header bounding rectangle
  Browser-->>FeatureContext: Return the viewport-top offset
Loading

Possibly related PRs

Suggested labels: Needs review

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the sticky header offset fix for pages with site alerts.
Linked Issues check ✅ Passed The changes address issue #308 by correcting sticky header positioning with site alerts and adding regression coverage.
Out of Scope Changes check ✅ Passed All code and test changes directly support the sticky header offset fix described in issue #308.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/308-sticky-hdr-alerts

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 phpcs (3.13.6)
tests/behat/bootstrap/FeatureContext.php

ERROR: Referenced sniff "Drupal" does not exist.
ERROR: Referenced sniff "DrupalPractice" does not exist.
ERROR: Referenced sniff "PHPCompatibility" does not exist.
ERROR: Referenced sniff "DrevOps" does not exist.
ERROR: Referenced sniff "SlevomatCodingStandard.TypeHints.DNFTypeHintFormat" does not exist.
ERROR: Referenced sniff "Drupal.Arrays.Array.LongLineDeclaration" does not exist.
ERROR: Referenced sniff "DrupalPractice.General.ClassName.ClassPrefix" does not exist.

Run "phpcs --help" for usage information

🔧 Stylelint (17.14.0)
web/themes/custom/drevops/components/04-templates/page/page.scss

ConfigurationError: Could not find "@stylistic/stylelint-plugin". Do you need to install the package or use the "configBasedir" option?
at getModulePath (file:///usr/local/lib/node_modules/stylelint/lib/utils/getModulePath.mjs:38:9)
at toAbsolutePath (file:///usr/local/lib/node_modules/stylelint/lib/augmentConfig.mjs:185:11)
at Array.map ()
at absolutizePaths (file:///usr/local/lib/node_modules/stylelint/lib/augmentConfig.mjs:192:44)
at augmentConfigBasic (file:///usr/local/lib/node_modules/stylelint/lib/augmentConfig.mjs:95:9)
at async augmentConfigFull (file:///usr/local/lib/node_modules/stylelint/lib/augmentConfig.mjs:138:24)
at async getConfigForFile (file:///usr/local/lib/node_modules/stylelint/lib/getConfigForFile.mjs:102:26)
at async resolveOptionValue (file:///usr/local/lib/node_modules/stylelint/lib/utils/resolveOptionValue.mjs:27:24)
at async standalone (file:///usr/local/lib/node_modules/stylelint/lib/standalone.mjs:127:22)


Comment @coderabbitai help to get the list of available commands.

@AlexSkrypnyk AlexSkrypnyk added the AUTOMERGE Pull request has been approved and set to automerge label Aug 12, 2026
@github-actions

Copy link
Copy Markdown

Code coverage (threshold: 80%)

  Classes: 48.00% (12/25)
  Methods: 73.42% (58/79)
  Lines:   87.77% (653/744)
Per-class coverage
Drupal\do_ai_alt_text\AltTextGenerator
  Methods:  85.71% ( 6/ 7)   Lines:  98.72% ( 77/ 78)
Drupal\do_ai_alt_text\Plugin\Action\RegenerateImageAltText
  Methods: 100.00% ( 8/ 8)   Lines: 100.00% ( 63/ 63)
Drupal\do_base\EventSubscriber\PreviewLinkCacheSubscriber
  Methods:  33.33% ( 1/ 3)   Lines:  11.11% (  1/  9)
Drupal\do_base\EventSubscriber\ThemeColorSubscriber
  Methods:  66.67% ( 2/ 3)   Lines:  40.00% (  4/ 10)
Drupal\do_base\Hook\AutomatedListPagerHook
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 31/ 31)
Drupal\do_base\Hook\MetatagsAlterHook
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% ( 58/ 58)
Drupal\do_base\Twig\ImageDimensionsExtension
  Methods:  40.00% ( 2/ 5)   Lines:  81.48% ( 44/ 54)
Drupal\do_content_api\EventSubscriber\JsonApiWriteGateSubscriber
  Methods:  66.67% ( 2/ 3)   Lines:  88.89% (  8/  9)
Drupal\do_content_api\Hook\EntityCreateAccessHook
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 12/ 12)
Drupal\do_content_api\Hook\ModerationPolicyHook
  Methods:  50.00% ( 1/ 2)   Lines:  93.75% ( 15/ 16)
Drupal\do_content_api\Routing\RouteSubscriber
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  6/  6)
Drupal\do_feed\FeedUrlBuilder
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 18/ 18)
Drupal\do_feed\Form\FeedSettingsForm
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 15/ 15)
Drupal\do_feed\Hook\EntityDeleteHook
  Methods:  50.00% ( 1/ 2)   Lines:  92.31% ( 12/ 13)
Drupal\do_feed\Hook\EntityPresaveHook
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 54/ 54)
Drupal\do_feed\Hook\PreprocessParagraphHook
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 14/ 14)
Drupal\do_feed\Hook\PreprocessViewsViewRowRssHook
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  3/  3)
Drupal\do_feed\Hook\ViewsPreViewHook
  Methods:  50.00% ( 1/ 2)   Lines:  96.43% ( 27/ 28)
Drupal\do_generated_content\Generator\CaseMatrix
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 12/ 12)
Drupal\do_generated_content\Generator\RelativeDate
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 84.51%. Comparing base (6382ee8) to head (e601d82).

Additional details and impacted files
@@           Coverage Diff            @@
##           develop     #309   +/-   ##
========================================
  Coverage    84.51%   84.51%           
========================================
  Files           26       26           
  Lines          562      562           
========================================
  Hits           475      475           
  Misses          87       87           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@AlexSkrypnyk AlexSkrypnyk added the Needs review Pull request needs a review from assigned developers label Aug 12, 2026
@AlexSkrypnyk
AlexSkrypnyk merged commit f9bfa5f into develop Aug 12, 2026
10 checks passed
@AlexSkrypnyk
AlexSkrypnyk deleted the feature/308-sticky-hdr-alerts branch August 12, 2026 02:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A1 Board worker 1 AUTOMERGE Pull request has been approved and set to automerge Needs review Pull request needs a review from assigned developers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorect sticky header offset when alerts are present on the page

2 participants