---
name: syncfusion-schedule-overflow-sticky-header
description: |
  Fix position:sticky not working on Syncfusion EJ2 Scheduler date header rows, and
  prevent row overlap caused by changing overflow on ancestor containers. Use when:
  (1) trying to make the date/time header row sticky in a Syncfusion Schedule with
  height:'auto' (page-scroll model), (2) changing overflow:hidden to overflow:visible
  on .schedule-calendar-container or .e-schedule causes employee rows to overlap,
  (3) sticky header works when applied via JS console but breaks layout when applied
  via CSS before Syncfusion init. Covers the hybrid CSS+JS gated-class pattern.
author: Claude Code
version: 1.0.0
date: 2026-04-14
---

# Syncfusion Schedule: Sticky Header + Overflow Layout Bug

## Problem

When using Syncfusion EJ2 Scheduler with `height: 'auto'` (page-scroll model), the
date/time header row scrolls away as users scroll through employee rows. Making it
`position: sticky` requires removing `overflow: hidden` from ancestor containers, but
doing this via CSS (before Syncfusion initialization) breaks Syncfusion's internal
row-height calculations, causing rows to overlap by ~40px.

## Context / Trigger Conditions

- Syncfusion EJ2 Schedule with `height: 'auto'` (page scrolls, not scheduler internally)
- Timeline views (TimelineDay, TimelineWeek, custom TimelineWeek)
- DOM structure: `.schedule-calendar-container` > `.card-body` > `#schedule-container.e-schedule`
- Both `.schedule-calendar-container` and `.e-schedule` have `overflow: hidden` (sticky blockers)
- Attempting `position: sticky` on `.e-schedule-table > tbody > tr:first-child`
- **Symptom**: Open Shifts row overlaps with the first employee row by ~40px when
  overflow is changed via CSS

## Root Cause

Syncfusion calculates row heights and positions during `appendTo()` initialization
based on the current overflow context of ancestor containers. When `overflow: hidden`
is changed to `overflow: visible` via CSS (which loads before Syncfusion init), the
Block Formatting Context changes, and Syncfusion's internal layout math produces
incorrect row positions.

Critically, applying the same overflow change via JS *after* init works perfectly
because Syncfusion has already completed its layout calculations.

## Solution: Hybrid CSS + JS Gated-Class Pattern

### 1. CSS: Gate sticky rules behind a class

```css
/* Sticky rules only activate when JS adds the gate class */
.e-schedule.sticky-header-enabled .e-table-container .e-schedule-table > tbody > tr:first-child {
    position: sticky;
    top: 40px;   /* Fixed navbar height */
    z-index: 10; /* Above shift blocks, below modals */
}

.e-schedule.sticky-header-enabled .e-table-container .e-schedule-table > tbody > tr:first-child > td {
    background: var(--bs-white, #ffffff);
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
}

/* Print reset */
@media print {
    .e-schedule .e-table-container .e-schedule-table > tbody > tr:first-child {
        position: static !important;
    }
    .e-schedule .e-table-container .e-schedule-table > tbody > tr:first-child > td {
        box-shadow: none !important;
    }
}
```

### 2. JS: Apply overflow changes AFTER Syncfusion init

```javascript
_enableStickyHeader() {
    const container = document.querySelector('.schedule-calendar-container');
    const scheduleEl = document.getElementById(this.containerId);

    if (container) {
        container.style.overflow = 'visible';
    }
    if (scheduleEl) {
        scheduleEl.style.overflow = 'visible';
        scheduleEl.classList.add('sticky-header-enabled');
    }
}
```

### 3. Call timing

- **After init**: Call after `schedule.appendTo()` + data load + `hideLoading()`
- **After view changes**: Syncfusion rebuilds DOM on view changes (`onActionComplete`
  with `requestType === 'viewNavigate'`), so re-call in each setTimeout branch

## Verification

1. Page loads: No row overlap, all employee rows properly spaced
2. Scroll down: Date header pins below navbar, employee rows scroll underneath
3. Switch views (Day, Week, 2 Weeks, Month): Sticky persists after each change
4. Month -> Week round-trip: Layout correct after grouping toggle
5. Console: No new JS errors
6. DOM check: `getComputedStyle(headerRow).position === 'sticky'`

## Key Insight

**CSS-only approach fails because Syncfusion's init-time layout depends on the
overflow context.** The overflow change MUST happen after `appendTo()` completes.
This is why injecting styles via browser DevTools (post-init) works but adding
the same CSS rules to a stylesheet (pre-init) breaks layout.

## Notes

- The `border-collapse: separate` on `.e-schedule-table` is required for sticky to
  work on `<tr>` elements (sticky doesn't work with `border-collapse: collapse`)
- z-index: 10 is chosen to be above shift blocks but below Bootstrap modals (1050+)
  and the fixed navbar (1030)
- The method is idempotent (safe to call multiple times)
- `overflow: clip` was also tested as an alternative to `visible` but produced the
  same row overlap issue when applied via CSS
