diff --git a/components/collapsible-panel/collapsible-panel.js b/components/collapsible-panel/collapsible-panel.js
index 8ace4071b6d..2791d2283f9 100644
--- a/components/collapsible-panel/collapsible-panel.js
+++ b/components/collapsible-panel/collapsible-panel.js
@@ -173,15 +173,20 @@ class CollapsiblePanel extends SkeletonMixin(FocusMixin(LitElement)) {
border-radius: 0;
outline-offset: -2px;
}
+
+ .d2l-collapsible-panel-top-sentinel {
+ position: relative;
+ top: calc(-1 * var(--d2l-page-sticky-top, 0px));
+ }
.d2l-collapsible-panel.scrolled .d2l-collapsible-panel-header {
background-color: var(--d2l-theme-background-color-base);
box-shadow: 0 8px 12px -9px rgba(0, 0, 0, 0.3);
position: sticky;
- top: 0;
+ top: var(--d2l-page-sticky-top, 0);
z-index: 11; /* must be greater greater than list-items with open dropdowns or tooltips */
}
.d2l-collapsible-panel.focused.scrolled .d2l-collapsible-panel-header {
- top: 2px;
+ top: calc(var(--d2l-page-sticky-top, 0px) + 2px);
}
.d2l-collapsible-panel-title {
flex: 1;
diff --git a/components/page/demo/page-component.js b/components/page/demo/page-component.js
index c6c9d7e4428..ec95c7c89ed 100644
--- a/components/page/demo/page-component.js
+++ b/components/page/demo/page-component.js
@@ -19,6 +19,7 @@ import '../../list/list-controls.js';
import '../../list/list-item.js';
import '../../list/list-item-content.js';
import '../../list/list-item-nav.js';
+import '../../more-less/more-less.js';
import '../../selection/selection-action.js';
import '../../switch/switch-visibility.js';
import '../../switch/switch.js';
@@ -242,153 +243,7 @@ class PageDemo extends LitElement {
${this.#renderDemoMainControls()}
I'm in the default slot of the d2l-page component!
- List with Sticky Controls (extend-separators)
-
-
-
-
-
-
-
- Introduction to Economics
- Chapter 1 — Fundamentals
- Due: May 15, 2026
-
-
-
-
- Supply and Demand
- Chapter 2 — Market Forces
- Due: May 22, 2026
-
-
-
-
- Market Equilibrium
- Chapter 3 — Price Discovery
- Due: May 29, 2026
-
-
-
-
- List with Sticky Controls (no extend-separators)
-
-
-
-
-
-
-
- Assignment 1: Research Proposal
- Weight: 20%
- Submissions: 14/30
-
-
-
-
- Assignment 2: Literature Review
- Weight: 30%
- Submissions: 8/30
-
-
-
-
- Assignment 3: Final Paper
- Weight: 50%
- Submissions: 0/30
-
-
-
-
- Table with Sticky Controls
-
-
-
-
-
-
-
-
- | Course |
- Enrolled |
- Completion Rate |
- Avg Grade |
-
-
-
-
- | Introduction to Biology |
- 145 |
- 87% |
- B+ |
-
-
- | Advanced Chemistry |
- 62 |
- 79% |
- B |
-
-
- | World History |
- 98 |
- 92% |
- A- |
-
-
-
-
-
- Sticky Table
-
-
-
-
- | Student |
- Assignment 1 |
- Assignment 2 |
- Final Exam |
- Total |
-
-
-
-
- | Alice Johnson |
- 92 |
- 88 |
- 95 |
- 91.7 |
-
-
- | Bob Smith |
- 85 |
- 79 |
- 82 |
- 82.0 |
-
-
- | Carol Davis |
- 78 |
- 91 |
- 87 |
- 85.3 |
-
-
- | David Lee |
- 95 |
- 93 |
- 90 |
- 92.7 |
-
-
- | Emily Chen |
- 88 |
- 84 |
- 91 |
- 87.7 |
-
-
-
-
+ ${this.#renderTableAndListStickyExamples()}
End of Content
@@ -566,10 +421,227 @@ class PageDemo extends LitElement {
+
+ ${this.#renderTableAndListStickyExamples()}
+
End of Content
` : nothing;
}
+
+ #renderTableAndListStickyExamples() {
+ return html`
+ List with Sticky Controls (extend-separators)
+
+
+
+
+
+
+
+ Introduction to Economics
+ Chapter 1 — Fundamentals
+ Due: May 15, 2026
+
+
+
+
+ Supply and Demand
+ Chapter 2 — Market Forces
+ Due: May 22, 2026
+
+
+
+
+ Market Equilibrium
+ Chapter 3 — Price Discovery
+ Due: May 29, 2026
+
+
+
+
+ List with Sticky Controls (no extend-separators)
+
+
+
+
+
+
+
+ Assignment 1: Research Proposal
+ Weight: 20%
+ Submissions: 14/30
+
+
+
+
+ Assignment 2: Literature Review
+ Weight: 30%
+ Submissions: 8/30
+
+
+
+
+ Assignment 3: Final Paper
+ Weight: 50%
+ Submissions: 0/30
+
+
+
+
+ Table with Sticky Controls
+
+
+
+
+
+
+
+
+ | Course |
+ Enrolled |
+ Completion Rate |
+ Avg Grade |
+
+
+
+
+ | Introduction to Biology |
+ 145 |
+ 87% |
+ B+ |
+
+
+ | Advanced Chemistry |
+ 62 |
+ 79% |
+ B |
+
+
+ | World History |
+ 98 |
+ 92% |
+ A- |
+
+
+
+
+
+ Sticky Table
+
+
+
+
+ | Student |
+ Assignment 1 |
+ Assignment 2 |
+ Final Exam |
+ Total |
+
+
+
+
+ | Alice Johnson |
+ 92 |
+ 88 |
+ 95 |
+ 91.7 |
+
+
+ | Bob Smith |
+ 85 |
+ 79 |
+ 82 |
+ 82.0 |
+
+
+ | Carol Davis |
+ 78 |
+ 91 |
+ 87 |
+ 85.3 |
+
+
+ | David Lee |
+ 95 |
+ 93 |
+ 90 |
+ 92.7 |
+
+
+ | Emily Chen |
+ 88 |
+ 84 |
+ 91 |
+ 87.7 |
+
+
+
+
+
+ Sticky Table with Sticky Controls
+
+
+
+
+
+
+
+
+ | Department |
+ Q1 Revenue |
+ Q2 Revenue |
+ Q3 Revenue |
+ Q4 Revenue |
+ Annual Total |
+
+
+
+
+ | Engineering |
+ $1.2M |
+ $1.4M |
+ $1.3M |
+ $1.5M |
+ $5.4M |
+
+
+ | Marketing |
+ $800K |
+ $920K |
+ $870K |
+ $950K |
+ $3.5M |
+
+
+ | Sales |
+ $2.1M |
+ $2.3M |
+ $2.0M |
+ $2.5M |
+ $8.9M |
+
+
+ | Support |
+ $450K |
+ $480K |
+ $460K |
+ $500K |
+ $1.9M |
+
+
+ | Research |
+ $680K |
+ $710K |
+ $730K |
+ $760K |
+ $2.9M |
+
+
+
+
+ `;
+ }
}
customElements.define('d2l-page-demo', PageDemo);
diff --git a/components/page/page-main.js b/components/page/page-main.js
index ad82de676ea..4c11882fc11 100644
--- a/components/page/page-main.js
+++ b/components/page/page-main.js
@@ -13,6 +13,13 @@ class PageMain extends PagePanelMixin(LitElement) {
.panel-header {
top: var(--d2l-page-header-height, 0);
}
+ .panel {
+ /* stylelint-disable-next-line length-zero-no-unit -- used in calc() by consumers */
+ --d2l-page-sticky-top: var(--d2l-page-header-height, 0px);
+ }
+ .panel.header-sticky {
+ --d2l-page-sticky-top: calc(var(--d2l-page-header-height, 0px) + var(--d2l-page-panel-header-height, 0px));
+ }
`];
render() {
diff --git a/components/page/page-panel-mixin.js b/components/page/page-panel-mixin.js
index 5efa230b071..ffcddeaaa50 100644
--- a/components/page/page-panel-mixin.js
+++ b/components/page/page-panel-mixin.js
@@ -27,8 +27,15 @@ export const pagePanelStyles = css`
}
.panel {
+ /* stylelint-disable-next-line length-zero-no-unit -- used in calc() by consumers */
+ --d2l-page-sticky-top: 0px;
padding: 30px;
}
+
+ .panel.header-sticky {
+ --d2l-page-panel-header-height: 70px;
+ --d2l-page-sticky-top: 70px;
+ }
`;
export const PagePanelMixin = superclass => class extends superclass {
diff --git a/components/selection/selection-controls.js b/components/selection/selection-controls.js
index d164e68ea46..a06912cf432 100644
--- a/components/selection/selection-controls.js
+++ b/components/selection/selection-controls.js
@@ -50,7 +50,7 @@ export class SelectionControls extends PageableSubscriberMixin(SelectionObserver
:host {
display: block;
position: sticky;
- top: 0;
+ top: var(--d2l-page-sticky-top, 0);
}
:host([no-sticky]) {
position: static;
diff --git a/components/table/table-wrapper.js b/components/table/table-wrapper.js
index 780249f5348..5a30bf1529c 100644
--- a/components/table/table-wrapper.js
+++ b/components/table/table-wrapper.js
@@ -241,7 +241,7 @@ export const tableStyles = css`
d2l-table-wrapper[sticky-headers][sticky-headers-scroll-wrapper] .d2l-table > thead {
display: block;
position: sticky;
- top: calc(var(--d2l-table-sticky-top, 0px) + var(--d2l-table-border-radius-sticky-offset, 0px));
+ top: calc(var(--d2l-page-sticky-top, 0px) + var(--d2l-table-sticky-top, 0px) + var(--d2l-table-border-radius-sticky-offset, 0px));
z-index: 2;
}
@@ -382,7 +382,7 @@ export class TableWrapper extends PropertyRequiredMixin(PageableMixin(SelectionM
}
.d2l-sticky-headers-backdrop {
position: sticky;
- top: calc(var(--d2l-table-sticky-top, 0px) + var(--d2l-table-border-radius));
+ top: calc(var(--d2l-page-sticky-top, 0px) + var(--d2l-table-sticky-top, 0px) + var(--d2l-table-border-radius));
width: 100%;
z-index: 2; /* Must sit under d2l-table sticky-headers but over sticky columns and regular cells */
}
@@ -832,7 +832,8 @@ export class TableWrapper extends PropertyRequiredMixin(PageableMixin(SelectionM
const stickyRows = Array.from(this._table.querySelectorAll(SELECTORS.headers));
stickyRows.forEach(r => {
- const thTop = hasStickyControls ? `${rowTop}px` : `calc(${rowTop}px + var(--d2l-table-border-radius-sticky-offset, 0px))`;
+ const borderRadiusOffset = hasStickyControls ? '' : ' + var(--d2l-table-border-radius-sticky-offset, 0px)';
+ const thTop = `calc(${rowTop}px + var(--d2l-page-sticky-top, 0px)${borderRadiusOffset})`;
const ths = Array.from(r.querySelectorAll('th,td'));
ths.forEach(th => th.style.top = thTop);