Search code examples
htmlcssalignmentcss-position

CSS absolute/relative positioning in flyout menu: How to use only part of parents' properties (left,top,width,vertical/horizontal alignment)?


This is a problem which I stumbled upon recently quite often in CSS. Lets assume we have a flyout menu somewhere in the page which has list items. Those list items have sub-menus which are shown on hover. Because I want the sub-menu to appear right below the parent list item I give position:relative; to the parent item and position:absolute;top:0; to the submenu. So far this works great.

However because of position:relative; now I have lost the ability to make the submenu 100% width of page width and to use left:0; in the sense that the submenu is aligned to the very left of the page not of the parent element.

What I want is to somehow have position:relative; on the parent for the vertical alignment, but position:inherit; regarding the horizontal alignment.

See the following example - the submenus are aligned correctly in vertical terms but should start to the very left in horizontal terms:

.clearfix::after {
    content: "";
    clear: both;
    display: table;
}

#my-menu-inner > ul {
  width:100%;
  background-color:yellow;
  list-style-type:none;
}

#my-menu-inner > ul > li {
  float:left;
  position:relative;
  padding:20px;
  border:1px solid black;
  margin:20px;
}

#my-menu-inner > ul > li > div.sub {
   position:absolute;
   top:60px;
   background-color:red;
   padding:40px;
   display:none;
   left:0;
   width:100vw;
}

#my-menu-inner > ul > li:hover > div.sub, #my-menu-inner > ul > li:focus > div.sub {
    display:block;
}
<div id="whatever">Just something before ...</div>
<div id="my-menu">
  <div id="my-menu-inner">
    <ul class="clearfix">
      <li>
        <span>foo</span>
        <div class="sub">
          <ul>
            <li>hello</li>
            <li>world</li>
          </ul>
        </div>
      </li>
      <li>
        <span>foo</span>
        <div class="sub">
          <ul>
            <li>please</li>
            <li>alignme</li>
          </ul>
        </div>
      </li>
    </ul>
  </div>
</div>

How can I achieve what I want (please only pure CSS, answers involving JS will not be accepted)?


Solution

  • Make the menu item positioned relatively to the ul rather than the li:

    .clearfix::after {
        content: "";
        clear: both;
        display: table;
    }
    
    #my-menu-inner > ul {
      margin:10px;
      width:100%;
      background-color:yellow;
      list-style-type:none;
      position:relative;
    }
    
    #my-menu-inner > ul > li {
      float:left;
      padding:20px;
      border:1px solid black;
      margin:20px;
    }
    
    #my-menu-inner > ul > li > div.sub {
       position:absolute;
       top:calc(100%  - 20px);
       background-color:red;
       padding:40px;
       display:none;
       left:0;
       width:100vw;
    }
    
    #my-menu-inner > ul > li:hover > div.sub, #my-menu-inner > ul > li:focus > div.sub {
        display:block;
    }
    <div id="whatever">Just something before ...</div>
    <div id="my-menu">
      <div id="my-menu-inner">
        <ul class="clearfix">
          <li>
            <span>foo</span>
            <div class="sub">
              <ul>
                <li>hello</li>
                <li>world</li>
              </ul>
            </div>
          </li>
          <li>
            <span>foo</span>
            <div class="sub">
              <ul>
                <li>please</li>
                <li>alignme</li>
              </ul>
            </div>
          </li>
        </ul>
      </div>
    </div>