0

I'm working with a layout where a parent container has overflow: auto, which causes it to clip any inner elements that overflow, expected behavior.

However, I have a dropdown menu inside this scrollable container that I want to visually overlap the container, appearing outside of it. At the same time I want the dropdown to stay anchored to its parent element as it scrolls.

Is it possible to structure the layout so that:

  1. The dropdown menu is not clipped by the scrollable parent.

  2. The dropdown still moves with the content as the user scrolls the container

Here’s a minimal reproduction of the issue. Here, the blue .dropdown div is clipped by the .fixedScrollable container:

https://jsfiddle.net/htL51dmo/49/

.fixedScrollable {
  height: 300px;
  width: 200px;
  padding: 10px;
  background-color: lightblue;
  overflow: auto;
}

.container1 {
  background-color: lightslategrey;
  height: 500px;
  padding: 10px;
  padding-top: 100px;
}

.container2 {
  position: relative;
  background-color: pink;
  height: 50px;
}

.dropdown {
  position: absolute;
  background-color: blue;
  height: min-content;
  width: 100px;
  left: 80%;
  top: 100%;
}
<div class="fixedScrollable">
  <div class="container1">
    <div class="container2">
      <div class="dropdown">
        Dropdown menu
      </div>
    </div>
  </div>
</div>

1

2 Answers 2

0

The issue is the stacking context here, but if you move the element, it's no longer tied to its parent position as it scrolls.

The future CSS Anchor Positioning API can fix this, but it has limited support as of today. There is a polyfill available though.

Anchor Positioning Demo:

.fixedScrollable {
  height: 300px;
  width: 200px;
  padding: 10px;
  background-color: lightblue;
  overflow: auto;
}

.container1 {
  background-color: lightslategrey;
  height: 500px;
  padding: 10px;
  padding-top: 100px;
}

.container2 {
  position: relative;
  background-color: pink;
  height: 50px;
  /* define the anchor name */
  anchor-name: --dropdown-anchor;
}

.dropdown {
  background-color: blue;
  height: min-content;
  width: 100px;
  position: absolute;

  /* reset all positioning */
  inset: 0;
  /* override margin defaults */
  margin: initial;
  /* position on anchor */
  top: anchor(bottom);
  left: anchor(right);

  position-anchor: --dropdown-anchor;
}
<!-- Add polyfill (normally in document <head>) -->
<script type="module">
  if (!("anchorName" in document.documentElement.style)) {
    import("https://unpkg.com/@oddbird/css-anchor-positioning");
  }
</script>

<div class="fixedScrollable">
  <div class="container1">
    <!-- <div class="dropdown">Dropdown menu</div> -->
    <div class="container2"></div>
  </div>
</div>

<!-- 
  Move the dropdown outside of the stacking context 
  that is causing the clipping 
-->
<div class="dropdown">Dropdown menu</div>

While this works, it means that the hierarchy of your html is lost and it introduces some potentially big accessibility issues.

Instead of moving the element out of the stacking context by changing its source order, you could take advantage of the browser top layer by utilizing either a <dialog> element or popover attribute. Assuming that the element you called your "dropdown" is dismissible anyway, the Popover API makes it easy to create a menu that can be opened and closed with potentially no JS at all as shown in the demo below.

Anchor Positioning + Popover Demo:

.fixedScrollable {
  height: 300px;
  width: 200px;
  padding: 10px;
  background-color: lightblue;
  overflow: auto;
}

.container1 {
  background-color: lightslategrey;
  height: 500px;
  padding: 10px;
  padding-top: 100px;
}

.container2 {
  position: relative;
  background-color: pink;
  height: 50px;

  anchor-name: --dropdown-anchor;
}

.dropdown {
  background-color: blue;
  height: min-content;
  width: 100px;
  position: absolute;

  /* reset all positioning */
  inset: 0;
  /* override margin defaults */
  margin: initial;
  /* position on anchor */
  top: anchor(bottom);
  left: anchor(right);

  position-anchor: --dropdown-anchor;
}

.dropdown:popover-open {
  /* styles to be applied to the
  dropdown in the open state
  (great for transitions and 
  animations)
  */
}
<script type="module">
  if (!("anchorName" in document.documentElement.style)) {
    import("https://unpkg.com/@oddbird/css-anchor-positioning");
  }
</script>

<div class="fixedScrollable">
  <div class="container1">
    <button class="container2" popovertarget="dropdown">
      click to display dropdown
    </button>
    <div popover class="dropdown" id="dropdown">
      Dropdown menu
    </div>
  </div>
</div>

Sign up to request clarification or add additional context in comments.

Comments

0

If you don't want to use a CSS hack or JavaScript to make the dropdown work, use <details> and <summary>, the show/hide behavior is baked in. In the example below position: fixed is used to keep <details> in the same position when the user scrolls the page.

Stack Snippet Editor has been crippled, it no longer has the ability to display anything taller than 290px properly. Go to this CodePen instead.

*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
}

:root {
  font: 2ch/1.5 "Segoe UI";
}

body {
  width: 100vw;
  min-height: 100vh;
  overflow-x: hidden;
  overflow-y: scroll;
}

header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
  padding: 1rem;
  border-bottom: 2.5px solid #222;
}

hgroup {
  display: flex;
  gap: 1rem;
}

/* Dropdown [START] */
details {
  /* Takes <details> out of the document flow.    
  Everything in DOM cannot affect <details> space
  and vice versa.
  */
  position: fixed;
  /* Position is in relation to viewport      
  (eg entire visible area) so <details> top left
  corner is located 1rem below the top edge of
  viewport and <details> right edge is 2rem to
  the left of viewport right edge.
  */
  top: 1rem;
  right: 2rem;
  width: 7rem;
  padding: 0 0.25rem 1rem;
  background: #ccc;
}

summary {
  cursor: pointer;
  padding: 1rem 0.75rem 0 0.5rem;
}

menu {
  list-style: none;
  margin-top: 0.5rem;
  padding: 0;
  padding-inline-start: 1rem;
}

/* Dropdown [END] */

main {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  width: 100%;
  height: 100%;
  padding: 1rem;
}
<header>
  <hgroup>
    <figure>
      <img src="https://i.ibb.co/c1PtcM7/matrix1.gif" alt="matrix1" width="48" height="48">
    </figure>
    <h1>Website Title</h1>
  </hgroup>

  <nav>
    <!-- Dropdown [START] -->
    <details>
      <summary>Menu</summary>
      <menu>
        <li><a href="#">Item 1</a></li>
        <li><a href="#">Item 2</a></li>
        <li><a href="#">Item 3</a></li>
        <li><a href="#">Item 4</a></li>
        <li><a href="#">Item 5</a></li>
        <li><a href="#">Item 6</a></li>
        <li><a href="#">Item 7</a></li>
        <li><a href="#">Item 8</a></li>
        <li><a href="#">Item 9</a></li>
      </menu>
    </details>
    <!-- Dropdown [END] -->
  </nav>
</header>
<main>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
  <p>Content</p>
</main>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.