Pin list headers flush to the top of the scroll area

Sticky headers pinned at the content area's padding edge, leaving a strip
above them where scrolled-past rows showed through. Offset them by that
padding. In grouped lists, column headers now pin below the sticky group
heading instead of behind it.

Closes #5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-26 20:59:35 -05:00
parent ef1579c978
commit 1a23bd83a4

View File

@@ -774,9 +774,14 @@ input[type="checkbox"] {
color: var(--warn); color: var(--warn);
} }
.content { .content {
/* Named so sticky headers can cancel it: sticky elements pin at the scroll
container's padding edge, which would leave a strip above them where
scrolled-past rows still show. */
--content-pad-top: var(--space-5);
--group-header-h: 40px;
flex: 1; flex: 1;
overflow: auto; overflow: auto;
padding: var(--space-5) var(--space-6); padding: var(--content-pad-top) var(--space-6);
} }
/* Empty / welcome state */ /* Empty / welcome state */
@@ -960,10 +965,18 @@ h4 {
* tag) since h3 is also used for .dialog-title, which must not be sticky. */ * tag) since h3 is also used for .dialog-title, which must not be sticky. */
.group-header { .group-header {
position: sticky; position: sticky;
top: 0; top: calc(-1 * var(--content-pad-top, 0px));
z-index: 5; z-index: 5;
margin: var(--space-4) 0 var(--space-2); margin: var(--space-4) 0 var(--space-2);
padding: var(--space-2) 0; /* A fixed height, so a grouped table's column headers can pin just below
it (see `.group-header + table th`). */
height: var(--group-header-h);
box-sizing: border-box;
display: flex;
align-items: center;
gap: var(--space-2);
white-space: nowrap;
padding: 0;
background: var(--bg-app); background: var(--bg-app);
font-size: var(--fs-body); font-size: var(--fs-body);
font-weight: 700; font-weight: 700;
@@ -1569,7 +1582,7 @@ th {
letter-spacing: var(--tracking-label); letter-spacing: var(--tracking-label);
color: var(--muted); color: var(--muted);
position: sticky; position: sticky;
top: 0; top: calc(-1 * var(--content-pad-top, 0px));
background: var(--bg-content); background: var(--bg-content);
border-bottom: 1px solid var(--border); border-bottom: 1px solid var(--border);
z-index: 1; z-index: 1;
@@ -1579,6 +1592,10 @@ td {
border-bottom: 1px solid var(--border-soft); border-bottom: 1px solid var(--border-soft);
vertical-align: middle; vertical-align: middle;
} }
/* Under a sticky group header, pin below it instead of behind it. */
.group-header + table th {
top: calc(var(--group-header-h) - var(--content-pad-top, 0px));
}
tbody tr { tbody tr {
transition: background 0.08s; transition: background 0.08s;
} }
@@ -3291,7 +3308,8 @@ button.pull-action {
placement on Android). */ placement on Android). */
@media (max-width: 720px) { @media (max-width: 720px) {
.content { .content {
padding: var(--space-4); --content-pad-top: var(--space-4);
padding: var(--content-pad-top);
/* Clear the fixed bottom nav, or its last row of content is hidden /* Clear the fixed bottom nav, or its last row of content is hidden
behind it — the safe-area inset is already baked into the nav's own behind it — the safe-area inset is already baked into the nav's own
height, so it isn't added again here. */ height, so it isn't added again here. */