Center/align vertical list on active item

Viewed 329

This question is out of curiosity, to explore the limits of CSS and get a better understand of the layout possibilities. Consider the following example:

.container {
   display: inline-block;
   width: 6rem;
   vertical-align: -4rem;
}

.item {
  width: 6rem;
  height: 2rem;
  background-color: lightblue;
  line-height: 2rem;
}

.item.active {
  color: white;
  background-color: darkblue;
}
  
The selected element: 
<div class="container">
  <div class="item a">Pineapple</div>
  <div class="item b active">Apple</div>
  <div class="item c">Tomato</div>
  <div class="item d">Banana</div>
</div>

Is it possible, with pure CSS, to always keep the active item aligned with the description text without hardcoding the number of items? In the example, you can see that the second item is active, and the description text is aligned to it.

My ideas:

  • Is there some way to only make elements before the active element contribute to the height of the box? Then vertical-align: bottom should do what I want. However, I haven't found a way to do that while keeping the stacking of items intact.
  • Floats don't always contribute to the height of a container, but inline-block ensures that all floats are contained. Perhaps there is a way to achieve this with floats and a few more levels of containers?
2 Answers

I realized you want the center align the left side text. You can do this using pseudo elements. Basically the pseudo element contains the text and it is positioned absolutely to the left(or right) of the list

.container {
   display: inline-block;
   flex-wrap: wrap;
   width: 6rem;
   margin-left: 10em;
}

.item {
  width: 6rem;
  height: 2rem;
  background-color: lightblue;
  line-height: 2rem;
  position: relative;
}

.item.active {
  color: white;
  background-color: darkblue;
}

.item.active::before {
  content: "The selected element: ";
  display: block;
  position: absolute;
  right: 105%;
  height: 100%;
  white-space: nowrap;
  color: black;
}
<div class="container">
  <div class="item a">Pineapple</div>
  <div class="item b active">Apple</div>
  <div class="item c">Tomato</div>
  <div class="item d">Banana</div>
</div>

<div class="container">
  <div class="item d">Orange</div>
  <div class="item a">Pineapple</div>
  <div class="item b">Apple</div>
  <div class="item c active">Tomato</div>
  <div class="item d">Banana</div>
</div>

This is a way to move list items around which i thought it was what you wanted but after reading the questions again, I don't think this is it. However I left it here because I thought it might give you some ideas.

The container is set to display inline flex, all items are set to order of 1 while the active item is 0. The order makes the items realign based on it and since the active item has a higher order, it will appear first

.select-text {
  display: inline-block;
}
.container {
   display: inline-flex;
   flex-wrap: wrap;
   width: 6rem;
}

.item {
  width: 6rem;
  height: 2rem;
  background-color: lightblue;
  line-height: 2rem;
  order: 1;
}

.item.active {
  color: white;
  background-color: darkblue;
  order: 0;
}
<div class="select-text">The selected element: </div>
<div class="container">
  <div class="item a">Pineapple</div>
  <div class="item b active">Apple</div>
  <div class="item c">Tomato</div>
  <div class="item d">Banana</div>
</div>

<br /><br />

<div class="select-text">The selected element: </div>
<div class="container">
  <div class="item a">Pineapple</div>
  <div class="item b">Apple</div>
  <div class="item c active">Tomato</div>
  <div class="item d">Banana</div>
  <div class="item d">Orange</div>
</div>

Here is a simple idea where you can keep your code and rely on the default baseline alignment. The trick is to make all the elements floating but not the active and this one will define the baseline of its parent.

It's also scalable and will work with any text length. It can even work with mutltiple select at the same row:

.container {
  display: inline-block;
  width: 6rem;
}

.item {
  width: 100%;
  background-color: lightblue;
  line-height: 2rem;
  float: left;
}

.item.active {
  color: white;
  background-color: darkblue;
  float: none;
  clear: left;
}


div:not([class]) {
  margin:5px;
  outline:1px solid red;
}
<div>The selected element:
<div class="container">
  <div class="item a">Pineapple</div>
  <div class="item b active">Apple</div>
  <div class="item c">Tomato</div>
  <div class="item d">Banana</div>
</div>
</div>

<div>The selected :
<div class="container">
  <div class="item a">Pineapple</div>
  <div class="item b">Apple</div>
  <div class="item c active">Tomato</div>
  <div class="item d">Banana</div>
</div>
</div>

<div>The selected element is the :
<div class="container">
  <div class="item a">Pineapple</div>
  <div class="item b">Apple</div>
  <div class="item c">Tomato</div>
  <div class="item d active">Banana</div>
</div>
The selected :
<div class="container">
  <div class="item a">Pineapple</div>
  <div class="item b active">Apple</div>
  <div class="item c">Tomato</div>
  <div class="item d">Banana</div>
</div>
</div>

The baseline of an 'inline-block' is the baseline of its last line box in the normal flow, unless it has either no in-flow line boxes or if its 'overflow' property has a computed value other than 'visible', in which case the baseline is the bottom margin edge ref

Make sure you don't add any oveflow property to your inline-block element:

Related