2

I am trying to build a header component using FlexBox. This is a picture of what I'm trying to achieve:

enter image description here

The red box is a flexbox. The green boxes are each elements within the flexbox. The right-most element (labeled 3) has a click event connected to it. The idea is when that box is clicked, a menu pops up immediately below it, but not within it.

I've tried using absolute and relative positioning of div's 3 and 4, but no combination is achieving what I'm looking for. The best I have been able to do is to use absolute positioning on div 4. It pops up seperarte from div 3, as intended, but the width of div 4 wont grow larger than div 3, and the content wraps.

How can I have a popup element in div 4 that takes it's width from its own content, and doesnt wrap?

John Dibling
  • 99,718
  • 31
  • 186
  • 324

3 Answers3

3

Assuming you have a simple markup structure like below, that will most likely be best achieved using an absolute positioned div

This will display your popup outside the parent, with no limits to the very same's width/height

I here chose to put the popup outside the 3:rd div, as it will give you more flexibility where to position it based on responsiveness (different screen sizes etc.)

Scriptbased version

document.querySelector('.click').addEventListener('click', function(e){
  e.target.nextElementSibling.classList.toggle('clicked');
})
.parent {
  position: relative;
  display: flex;
  border: 1px solid red;
}
.parent div {
  flex: 2;
  border: 1px solid lime;
  margin: 1px;
}
.parent div:nth-child(2) {
  flex: 3;
}
.parent div:nth-child(3) {
  flex: 2;
}
.parent .popup {
  display: none;
  position: absolute;
  right: -2px;
  top: calc(100% + 3px);
  border: 1px solid blue;
}
.parent .popup.clicked {
  display: block;
}
<div class="parent">
  <div> 1 </div>
  <div> 2 </div>
  <div class="click"> 3 <br> (click to toggle) </div>
  <div class="popup"> This one can have text <br>
        that does pretty much what you want    
  </div>
</div>

This can also be done without any script, using a label and a checkbox

Updated

The popup close when clicking anywhere in the page (Thanks to I Love CSS)

.parent {
  position: relative;
  display: flex;
  border: 1px solid red;
}
.parent div {
  flex: 2;
  border: 1px solid lime;
  margin: 1px;
}
.parent div:nth-child(2) {
  flex: 3;
}
.parent div:nth-child(3) {
  flex: 2;
}
.parent .popup {
  display: none;
  position: absolute;
  right: -2px;
  top: calc(100% + 3px);
  border: 1px solid blue;
}
.parent .modal {
  display: none;
  position: fixed;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  background: white;
  opacity: 0.3;
}
.parent .click label[for=chkbox] {
  display: block;  
}
#chkbox {
  display: none;
}
#chkbox:checked ~ .modal,
#chkbox:checked ~ .popup {
  display: block;
}
<div class="parent">
  <div> 1 </div>
  <div> 2 </div>
  <div class="click"><label for="chkbox"> 3 <br> (click to toggle) </label></div>
  <input type="checkbox" id="chkbox">
  <label class="modal" for="chkbox"></label>
  <div class="popup"> This one can have text <br>
        that does pretty much what you want    
  </div>
</div>

One more version without any script, using :focus

Updated

Made the popup persist using :hover, so also links work (Thanks to Andrei Gheorghiu)

.parent {
  position: relative;
  display: flex;
  border: 1px solid red;
}
.parent div {
  flex: 2;
  border: 1px solid lime;
  margin: 1px;
}
.parent div:nth-child(2) {
  flex: 3;
}
.parent div:nth-child(3) {
  flex: 2;
}
.parent .popup {
  display: none;
  position: absolute;
  right: -2px;
  top: calc(100% + 3px);
  border: 1px solid blue;
}
.click:focus + .popup {
  display: block;
}
.click + .popup:hover {
  display: block;
}
<div class="parent">
  <div> 1 </div>
  <div> 2 </div>
  <div class="click" tabindex="-1"> 3 <br> (click to toggle) </div>
  <div class="popup"> This one can have text <br>
        that does pretty much what you want <br><br>
        <a href="#" onclick="alert('hey');">links included</a>
  </div>
</div>
Community
  • 1
  • 1
Asons
  • 84,923
  • 12
  • 110
  • 165
  • The problem with the `pure css checkbox` toggle trick is you can't close it when click/tap-ing outside the dropdown. And that's the expected behavior in today's mobile web. Less than `1%` users have `javascript` disabled, but much more than `50%` expect a dropdown to close when click/tap-ing outside it. – tao Jan 02 '17 at 20:12
  • @AndreiGheorghiu Yes, and I added it to solely show script can be avoided...and since you mentioned it should disappear when one click anywhere on the page, I added a 3:rd sample doing just that, without script – Asons Jan 02 '17 at 20:28
  • Would you use that in production? As much as a right-click hides it. Right-click should not interfere with current state, right? Not to mention inability to select anything from dropdown. I love pure CSS solutions, but this is not one of those cases. – tao Jan 02 '17 at 20:35
  • @AndreiGheorghiu Thanks for your comment, though I really don't know what you mean with _right click_ in this case, simply left-click inside and then outside and it will work. And yes, I use such things in production when it solve what I want to achieve, and as I don't know what the OP wants, I posted options, and now it is for the OP to decide whether any of these is usable. – Asons Jan 02 '17 at 20:50
  • When the dropdown is open, if you right-click anywhere in the page, it closes and usually, when you right-click in a page you don't expect elements to change. After all, you're only opening a context-menu. Long story short, I wouldn't rely on `:focus` for displaying menu contents to users in a production environment. I apologise if I seemed too *chatty*. Enjoyed our talk. A happy new year! – tao Jan 02 '17 at 21:01
  • 2
    @AndreiGheorghiu I don't mind someone challenge my answers, on the contrary, it makes me think ... I do agree one should be careful when going _outside the box_ ... and you can select stuff as well, links included, updated 3:rd sample to show that too :) ... A happy new year for you too – Asons Jan 02 '17 at 21:12
  • The problem with `:focus` is that it stops working when clicking the link inside. `:focus-within` would be much better, if more browsers supported it. – Oriol Jan 03 '17 at 00:30
  • @Oriol Thanks, that was an interesting property I didn't know of. In my case I solved the _stop working when clicking the link inside_ issue and made the `popup` persist using `:hover`, so now even links work – Asons Jan 03 '17 at 06:29
  • You can actually close it when you click outside even if you use a pure css approach. See this for more information: http://stackoverflow.com/questions/40950099/close-modal-upon-click-outside-css-html-only –  Jan 03 '17 at 06:41
  • You can keep the right-click functionality without closing the popup @AndreiGheorghiu –  Jan 03 '17 at 06:49
  • I know. But at the time I made the comment, LGSon's solution was closing on right-click. That's why I made the comment. And I also know by now that by combining `:focus`,`:hover`,`:checked` and `:target` you can get a ~120 lines pure CSS solution that does what 2 lines of jQuery do. And guess what? I love CSS too :)). Too much, unfortunately.... – tao Jan 03 '17 at 06:59
  • @AndreiGheorghiu I made one in less than 120 lines (updated my 2:nd sample) ... and don't forget, the jQuery library is a lot more than 120 lines :) – Asons Jan 03 '17 at 07:06
  • 1
    @ILoveCSS Thanks for your comment/link ... updated my 2:nd sample – Asons Jan 03 '17 at 07:11
  • @AndreiGheorghiu Just a slight misunderstanding, I was referring to the method I linked too. But yeah, it will take more code to do it with CSS. Hoever, the result is a lighter page in the end with no external libraries and no extra http requests. –  Jan 03 '17 at 07:13
  • 1
    @ILoveCSS Now my 2:nd sample does the same as your link ... with less code though :) – Asons Jan 03 '17 at 07:14
  • Yeah! I like this answer because it gives a lot of options. Good work –  Jan 03 '17 at 07:18
  • 1
    @ILoveCSS, I was the first to vote it up and it wasn't nearly as complete as it is now ;) I actually think my comments made LGSon put a bit more effort in it than he initially set out to. But of course, I might be wrong. I'm just speculating here... – tao Jan 03 '17 at 07:50
  • @AndreiGheorghiu Yes, that is correct :) ... and thanks – Asons Jan 03 '17 at 08:13
  • This led me to the solution in my actual code. In the parent div I had an `overflow: hidden` in the SCSS which was messing me up. I thought the problem was related to my use of flexbox in the header, but your answer demonstrated that not to be the case. – John Dibling Jan 03 '17 at 13:11
3

EDIT 2: With this setup you do not need to hard-code any top values on dropdown, therefore gaining flexibility and maintaining alignment with navbar (even when resizing on different viewport widths).

$(".item-red").on("click", function() {
  $(".item-dropdown").toggleClass("display");
});
.container {
    display: flex;
    flex-flow: row wrap;
    text-align: center;
}
.item {
    flex: 2;
    padding: 5px;
    margin: 0;
}
.item-blue {
    background: lightblue;
}

.item-green {
    background: lightgreen;
    flex: 3;
}
.item-red {
    background: lightgray;
    flex: 1;
}
.item-red:hover {
    cursor: pointer;
}
.container-dropdown {
    display: flex;
    flex-flow: row wrap;
    position: relative;
    text-align: center;
}
.item-dropdown {
    width: 240px;
    display: none;
    background: gold;
    position: absolute;
    right: 0;
    z-index: 1;
    padding: 5px;
}
.display {
    display: flex;
}
.content {
    text-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
    <div class="item item-blue">Lorem</div>
    <div class="item item-green">Lorem ipsum dolor kjghj</div>
    <div class="item item-red">Lorem ipsum dolor
    </div>
</div>
<div class="container-dropdown">
    <div class="item-dropdown">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Fuga voluptate, ipsum consequuntur maiores unde laboriosam suscipit velit corporis.</div>
</div>
<div class="content">Content - lorem ipsum dolor sit amet, consectetur adipisicing elit. Quibusdam delen iti soluta qui, incidunt, neque est doloribus esse deserunt modi, mollitia delectus illum! Ullam nihil reiciendis animi eligendi nemo non. Incidunt.</div>
Syden
  • 8,425
  • 5
  • 26
  • 45
2

This is normal behavior of dropdowns and it doesn't really matter how the "menu bar" items are displayed (flex, boxes or table). The conditions are:

  • parent has position:relative;
  • child has position:absolute; top: 100%;
  • whether or not you want the child to be visible on hover or if the parent has a certain class applied to it is a matter of UI/UX and personal choice.

Using negative margins you can set the dropdowns to be larger than the parent. Or you could even make the them full page size and act like mega-menus (remember they are absolutely positioned).

Here's an example:

body {
  margin: 0;
  padding: 0;
  background-color: #f5f5f5;
}

.flex-menu {
  display: flex;
  background-color: white;
}
.flex-menu > * {
  position: relative;
  flex: 1 0 auto;
  padding: 10px;
  border-right: 1px solid #eee;
  cursor: pointer;
}
.flex-menu > *:last-child {
  border-right: none;
}
.flex-menu > * .submenu {
  position: absolute;
  top: 100%;
  left: 0;
  display: none;
  background-color: white;
  border-top: 1px solid #eee;
  padding: 10px;
  box-shadow: 0 1px 3px 0 rgba(0,0,0,.2), 0 1px 1px 0 rgba(0,0,0,.14), 0 2px 1px -1px rgba(0,0,0,.12)
}
.flex-menu .has-megamenu {
  position: static
}
.flex-menu > * .submenu.megamenu {
  width: 100vw;
  left: 0;
  top: 39px;
  box-sizing: border-box;
}
.flex-menu > *:hover .submenu {
  display: block;
}

.flex-menu > *:last-child .submenu {
  right: 0;
  margin-left: -100%;
}
<div class="flex-menu">
  <div>first item</div>
  <div>second item
    <div class="submenu">
      This is a dropdopwn content.
    </div>
  </div>
  <div class="has-megamenu">third item
    <div class="submenu megamenu">
      This is a mega menu dropdopwn content. You can put anything here. A full page, of content, if you want
    </div>
  </div>
  <div>fourth item
    <div class="submenu">
      This is a dropdopwn content.
    </div>
  </div>
</div>

As for your request:

that takes it's width from its own content, and doesnt wrap

Typically, you would create a "mega-menu" (full-content-width) child with transparent background and you would place the content as a child of this mega-menu either using float:right or flex. I assume you want this to eventually wrap if it reaches full-content width. Example:

var closeDrops = function(e) {
  $('.flex-menu div').removeClass('active');
}
$('body').on('click', closeDrops);

$('.flex-menu div').on('click', function(e){
  if (!$(this).hasClass('active')) {
    closeDrops();
  }
  e.stopPropagation();
  $(this).toggleClass('active');
})
body {
  margin: 0;
  padding: 0;
  background-color: #f5f5f5;
  min-height: 100vh;
  font-family: sans-serif;
}

.flex-menu {
  display: flex;
  position: relative;
}
.flex-menu > * {
  position: relative;
  flex: 1 0 auto;
  padding: 10px;
  border: solid white;
  border-width: 0 1px 1px 0;  
  cursor: pointer;
  transition: background-color .3s ease-in-out;
}
.flex-menu > *:last-child {
  border-right: none;
}
.flex-menu > * .submenu {
  position: absolute;
  top: 100%;
  left: 0;
  display: none;
  background-color: white;
  border-top: 1px solid #eee;
  padding: 10px;
  box-shadow: 0 1px 3px 0 rgba(0,0,0,.2), 0 1px 1px 0 rgba(0,0,0,.14), 0 2px 1px -1px rgba(0,0,0,.12)
}
.flex-menu .has-megamenu {
  position: static
}
.flex-menu > * .submenu.megamenu {
  width: 100vw;
  left: 0;
  box-sizing: border-box;
}
.flex-menu > *:hover .submenu,.flex-menu > *.active .submenu  {
  display: block;
}
.flex-menu > *.active, .flex-menu > *:hover {
  box-shadow: 0 1px 3px 0 rgba(0,0,0,.2), 0 1px 1px 0 rgba(0,0,0,.14), 0 2px 1px -1px rgba(0,0,0,.12);
  background-color: white;
}
.flex-menu > *.active{
  z-index: 1;
  
}
.flex-menu > *:hover {
  z-index: 2;
}
.has-megamenu:last-child .megamenu{
  background-color: transparent; padding: 0;
  box-shadow: none;
}
.has-megamenu:last-child .megamenu > * {
  box-shadow: 0 1px 3px 0 rgba(0,0,0,.2), 0 1px 1px 0 rgba(0,0,0,.14), 0 2px 1px -1px rgba(0,0,0,.12);
}
.placed-right {
  background-color: white;
  padding:10px;
  float: right;
}
@media (max-width: 500px) {
  .flex-menu {
    flex-direction: column;
  }
  .flex-menu > * .submenu {
    width: 100vw;
    box-sizing: border-box;
  }
  .placed-right { 
    float: none;
    width: 100%;
    display: block;
    box-sizing: border-box;
  }
  .flex-menu .has-megamenu {
    position: relative;
  }
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="flex-menu">
  <div>first item</div>
  <div>second item
    <div class="submenu">
      This is a dropdopwn content.
    </div>
  </div>
  <div class="has-megamenu">third item
    <div class="submenu megamenu">
      This is a mega menu dropdopwn content. You can put anything here. A full page, of content, if you want
    </div>
  </div>
  <div class="has-megamenu">fourth item
    <div class="submenu megamenu">
      <span class="placed-right">
        I am right-aligned and I don't care about my parent's width, ok? <hr />I'll only wrap when I don't fit in page.
      </span>
    </div>
  </div>
</div>

Note I added click-to-toggle for dropdowns in this snippet.

tao
  • 82,996
  • 16
  • 114
  • 150