Search code examples
cssreactjsflexboxsticky

Headers not sticking when scrolling using CSS Sticky property


I have a simple calendar view which statically can be coded as the React component below. There is a parent container and it contains two containers timeslot and days. I want them next to each other so I used flex property. Now I want container to be some fixed size and want the overflow items to scroll. When I scroll I want headers to stick. The timeHeader and dayHeader stops sticking after I scroll 40% of the vertical length.

Rendered Image

Not sticking header

import React from "react";
import "./Test.css";

function Test() {
    return (
        <div className="parent">
            <div className="timeslot">
                <div className="timeHeader">Time</div>
                <div>
                    <ul>
                        <li>7:00 a.m</li>
                        <li>8:00 a.m</li>
                        <li>9:00 a.m</li>
                        <li>10:00 a.m</li>
                        <li>11:00 a.m</li>
                        <li>12:00 p.m</li>
                        <li>1:00 p.m</li>
                        <li>2:00 p.m</li>
                        <li>3:00 p.m</li>
                        <li>4:00 p.m</li>
                        <li>5:00 p.m</li>
                        <li>6:00 p.m</li>
                        <li>7:00 p.m</li>
                        <li>8:00 p.m</li>
                        <li>9:00 p.m</li>
                        <li>10:00 p.m</li>
                    </ul>
                </div>
            </div>
            <div className="days">
                <div className="dayHeader">Days</div>
                <div className="dayslots">
                    <div>
                        <ul>
                            <li>1</li>
                            <li>1</li>
                            <li>1</li>
                            <li>1</li>
                            <li>1</li>
                            <li>1</li>
                            <li>1</li>
                            <li>1</li>
                            <li>1</li>
                            <li>1</li>
                            <li>1</li>
                            <li>1</li>
                            <li>1</li>
                            <li>1</li>
                            <li>1</li>
                            <li>1</li>
                        </ul>
                    </div>
                    <div>
                        <ul>
                            <li>2</li>
                            <li>2</li>
                            <li>2</li>
                            <li>2</li>
                            <li>2</li>
                            <li>2</li>
                            <li>2</li>
                            <li>2</li>
                            <li>2</li>
                            <li>2</li>
                            <li>2</li>
                            <li>2</li>
                            <li>2</li>
                            <li>2</li>
                            <li>2</li>
                            <li>2</li>
                        </ul>
                    </div>
                    <div>
                        <ul>
                            <li>3</li>
                            <li>3</li>
                            <li>3</li>
                            <li>3</li>
                            <li>3</li>
                            <li>3</li>
                            <li>3</li>
                            <li>3</li>
                            <li>3</li>
                            <li>3</li>
                            <li>3</li>
                            <li>3</li>
                            <li>3</li>
                            <li>3</li>
                        </ul>
                    </div>
                    <div>
                        <ul>
                            <li>4</li>
                            <li>4</li>
                            <li>4</li>
                            <li>4</li>
                            <li>4</li>
                            <li>4</li>
                            <li>4</li>
                            <li>4</li>
                            <li>4</li>
                            <li>4</li>
                            <li>4</li>
                            <li>4</li>
                            <li>4</li>
                            <li>4</li>
                            <li>4</li>
                            <li>4</li>
                        </ul>
                    </div>
                </div>
            </div>
        </div>
    );
}

export default Test;

and its CSS

.parent {
    display: flex;
    width: 400px;
    height: 100px;
    overflow: scroll;
}

.timeHeader {
    position: sticky;
    top: 0;
    background-color: white;
}

.timeslot {
    position: sticky;
    left: 0;
    z-index: 2;
    background: white;
}

.days {
    flex: 5;
}

li {
    min-width: 100px;
}

.dayslots {
    display: flex;
}

.dayHeader {
    background-color: white;
    position: sticky;
    top: 0;
}

The data inside the li tag, you can ignore it, it could be anything. All I want to fix is Time Header to stick all the time. Day header to stick when I scroll vertically and time i.e 7:00am... to stick when I scroll horizontally.


Solution

  • Now I want container to be some fixed size.

    For flex items to shrink over their content size to fit parent, set the the childs' min-width (or height) to 0, i.e., add min-width:0 on the timeslot and days container to allow them to fit the parent's width:400px.

    The timeHeader and dayHeader stops sticking after I scroll 40%.

    position:sticky makes the header to offset relative to its nearest scrolling ancestor (.parent) as expected. The reason it stops working after scrolling 40% is because the header is treated as "stuck" only before it meets the opposite edge of its containing block.

    I want the overflow items to scroll. When I scroll I want headers to stick.

    It's not clear how you want the overflow to look like, but instead of adding overflow on parent, it makes more sense to have each field scrolls individually. This can be achieved by adding overflow-y:scroll (or auto) to the two long lists instead. (.dayslots and time list container.) If you only want one scroll on parent, move it onto parent instead.

    Next, these two lists have to shrink to fit into parent so that overflow will work. This can be done by flex-box flex-direction:column, and again we need to add min-height:0 on the lists to allow them to shrink over their content size.

    The modified html and css will look like this: (There are tricks to hide the scroll bars, but they are outside the scope of this question)

    .parent {
      display: flex;
      width: 400px;
      height: 150px;
      /*remove this since display:flex is shrinking its contents, there won't be any overflow*/
      /*move overflow here if you want to scroll all childs under .parent together*/
      /* overflow: scroll;*/
    }
    
    .timeslot {
      /*removed because not needed anymore*/
      /* position: sticky;*/
      left: 0;
      z-index: 2;
      background: white;
      /* Allow it to shrink over its content width by .parent's display:flex*/
      min-width: 0;
      /*add the following 2 lines align the time header and contents*/
      display: flex;
      flex-direction: column;
    }
    
    .timeHeader,
    .dayHeader {
      /* removed because not needed anymore*/
      /* position: sticky; */
      /* top: 0; */
      background-color: white;
    }
    
    .timeslots,
    .dayslots {
      /*Again, allow it to shrink over its content height*/
      min-height: 0;
      overflow-y: scroll;
      display: flex;
    }
    
    
    /* Below CSS changed with the same logic above*/
    
    .days {
      min-width: 0;
      flex: 5;
      display: flex;
      flex-direction: column;
    }
    
    li {
      min-width: 100px;
    }
    <div class="parent">
      <div class="timeslot">
        <div class="timeHeader">Time</div>
        <div class="timeslots">
          <ul>
            <li>7:00 a.m</li>
            <li>8:00 a.m</li>
            <li>9:00 a.m</li>
            <li>10:00 a.m</li>
            <li>11:00 a.m</li>
            <li>12:00 p.m</li>
            <li>1:00 p.m</li>
            <li>2:00 p.m</li>
            <li>3:00 p.m</li>
            <li>4:00 p.m</li>
            <li>5:00 p.m</li>
            <li>6:00 p.m</li>
            <li>7:00 p.m</li>
            <li>8:00 p.m</li>
            <li>9:00 p.m</li>
            <li>10:00 p.m</li>
          </ul>
        </div>
      </div>
      <div class="days">
        <div class="dayHeader">Days</div>
        <div class="dayslots">
          <div>
            <ul>
              <li>1</li>
              <li>1</li>
              <li>1</li>
              <li>1</li>
              <li>1</li>
              <li>1</li>
              <li>1</li>
              <li>1</li>
              <li>1</li>
              <li>1</li>
              <li>1</li>
              <li>1</li>
              <li>1</li>
              <li>1</li>
              <li>1</li>
              <li>1</li>
            </ul>
          </div>
          <div>
            <ul>
              <li>2</li>
              <li>2</li>
              <li>2</li>
              <li>2</li>
              <li>2</li>
              <li>2</li>
              <li>2</li>
              <li>2</li>
              <li>2</li>
              <li>2</li>
              <li>2</li>
              <li>2</li>
              <li>2</li>
              <li>2</li>
              <li>2</li>
              <li>2</li>
            </ul>
          </div>
          <div>
            <ul>
              <li>3</li>
              <li>3</li>
              <li>3</li>
              <li>3</li>
              <li>3</li>
              <li>3</li>
              <li>3</li>
              <li>3</li>
              <li>3</li>
              <li>3</li>
              <li>3</li>
              <li>3</li>
              <li>3</li>
              <li>3</li>
            </ul>
          </div>
          <div>
            <ul>
              <li>4</li>
              <li>4</li>
              <li>4</li>
              <li>4</li>
              <li>4</li>
              <li>4</li>
              <li>4</li>
              <li>4</li>
              <li>4</li>
              <li>4</li>
              <li>4</li>
              <li>4</li>
              <li>4</li>
              <li>4</li>
              <li>4</li>
              <li>4</li>
            </ul>
          </div>
        </div>
      </div>
    </div>