azangru azangru - 1 month ago 6
CSS Question

Positioning an element in the top right corner of the parent element and ensuring wrapping around it

I would like to create a container for an arbitrary number of elements, with an expand/collapse button. The button should be in the top right corner of the container, and the elements inside the container should wrap around the button. Here's the idea:

enter image description here

I made a quick Codepen prototype of this element, but the only way I could come up with to position the button in the top right corner was

float: right
:

html:

<div class="container closed">
<button>Click</button>
<!-- some divs with class "child" inserted with js -->
</div>


css:

.container {
height: 80px;
width: 330px;
margin: auto;
}

button {
float: right;
margin: 1.3em 1.6em 0 0;
}

.closed {
overflow-y: hidden;
}

.child {
width: 50px;
height: 30px;
background-color: grey;
display: inline-block;
margin: 1em;
}


So this seems to work with
float: right
but floats are so passé :-) Is there a cleaner way of positioning the button? I thought flexbox would be able to do it, but couldn't figure out a way of achieving this with flexbox. Any advice?

UPD: Added the relevant html and css code.

Roy Roy
Answer

You can achieve this purely with CSS, if you use the flex property of CSS3's flexbox, just like you said. I made a small demo to prove the point.

The core part in CSS lies here:

  display: flex;
  flex-flow: row-reverse wrap;

The display: flex will stretch the content across the width available and the row-reverse will align the items from right-to-left (where LTR is default). Read more about flexbox at MDN.

The other part is the toggle of the height of the menu, I don't know how many items you (can) have, but in the demo I used a bit of vanilla JavaScript to expand and collapse the menu.

var mainNav = document.getElementById('menu-list');
var navToggle = document.getElementById('expandbutton');

function mainNavToggle() {
  mainNav.classList.toggle('expanded');
}

navToggle.addEventListener('click', mainNavToggle);
.menu {
  height: 60px;
}

.list,
.list-item {
  list-style: none;
  margin: 0;
  padding: 0;
}

.list {
  width: 100%;
  display: flex;
  flex-flow: row wrap;
  height: 50px;
  overflow: hidden;
}

.list-item {
  width: 25%;
  padding: 3px;
  box-sizing: border-box;
}

.btn {
  background-color: teal;
  display: block;
  padding: 10px;
  box-sizing: border-box;
  border-radius: 3px;
  margin: 5px 0;
  color: white;
  text-decoration: none;
}

.top-right {
  border: 0;
  width: 100%;
  display: block;
  background-color: red;
}

.list.expanded {
  height: 100px;
}

.list > .list-item:nth-last-child(n+4) ~ .list-button {
  order: 1;
}

.list > .list-item:nth-child(n+4) {
  order: 2;
}
<div class="menu">
  <ul class="list" id="menu-list">
    <li class="list-item"><a href="#" class="btn">Button</a></li>
    <li class="list-item"><a href="#" class="btn">Button</a></li>
    <li class="list-item"><a href="#" class="btn">Button</a></li>
    <li class="list-item"><a href="#" class="btn">Button</a></li>
    <li class="list-item"><a href="#" class="btn">Button</a></li>
    <li class="list-item"><a href="#" class="btn">Button</a></li>
    <li class="list-item list-button">
      <button href="#" id="expandbutton" class="btn top-right">Expand</button>
    </li>
  </ul>
</div>

EDIT

I updated the snippet with the flexbox property of order which really helps in this case (MDN reference).

.list > .list-item:nth-last-child(n+4) ~ .list-button {
  order: 1;
}

.list > .list-item:nth-child(n+4) {
  order: 2;
} 

The initial value of order is 0. If you want to move the order around, it has to be higher than the initial value, so that's why we need to set the item of .list-button to order: 1. All the elements after the button needs to get behind or under the button (in case of 4 elements per row), so the :nth-child(n+4) needs an order: 2. See the updated snippet for a small demo.