I need to design a navigation rendered dynamically by a CMS (so I have no control over HTML markup). The menu has a unknown, variable number of items, and all items have a variable, unknown width. I need to layout those menu items in one line with an equidistant spacing in between (easily done with flexbox) and with a visual separator exactly in the middle of the spacing (this is my unsolved problem).
Below is the code I've got so far. The HTML is just an example output, and I can't modify it. I'm also not allowed to use JS. The CSS is from myself and I can fully control it.
.mainnav {
width: 100%;
display: flex;
justify-content: space-between;
list-style-type: none;
margin: 0;
padding: 0;
border: 1px solid black;
}
.mainnav__item {
flex: 0 0 auto;
background-color: yellow;
position: relative;
white-space: nowrap;
}
.mainnav__item + .mainnav__item::before {
content: '';
width: 1px;
height: 100%;
background-color: red;
position: absolute;
left: -50%;
top: 0;
}
<ul class="mainnav">
<li class="mainnav__item">
<a class="mainnav__link" href="#">Short</a>
</li>
<li class="mainnav__item">
<a class="mainnav__link" href="#">Looooooooooooooong</a>
</li>
<li class="mainnav__item">
<a class="mainnav__link" href="#">Mediuuum</a>
</li>
<li class="mainnav__item">
<a class="mainnav__link" href="#">whatever else</a>
</li>
</ul>
My problem is the red separator line. It should be exactly in the middle, but I don't know how to calculate the horizontal position.
Can anybody tell me a CSS only solution for the delimiter placement?
The solution should work in common, up-to-date browsers (current version of [Android|Win] Chrome, [macOs|iOs] Safari, Firefox, Edge), but also in IE11
One idea is to consider display:contents;1 within the li element and keep the pseudo element in-flow (remove position:absolute). This will make the pseudo element and the a to be the flex items instead of the li element thus space-between will do the job.
Simply pay attention to the browser support since it's a new feature.
.mainnav {
width: 100%;
display: flex;
justify-content: space-between;
list-style-type: none;
margin: 0;
padding: 0;
border: 1px solid black;
}
.mainnav__item {
flex: 0 0 auto;
position: relative;
white-space: nowrap;
display:contents;
}
.mainnav__item a {
background:yellow;
}
.mainnav__item + .mainnav__item::before {
content: '';
width: 1px;
display:block;
background-color: red;
}
<ul class="mainnav">
<li class="mainnav__item">
<a class="mainnav__link" href="#">Short</a>
</li>
<li class="mainnav__item">
<a class="mainnav__link" href="#">Looooooooooooooong</a>
</li>
<li class="mainnav__item">
<a class="mainnav__link" href="#">Mediuuum</a>
</li>
<li class="mainnav__item">
<a class="mainnav__link" href="#">whatever else</a>
</li>
</ul>
1 The element itself does not generate any boxes, but its children and
pseudo-elements still generate boxes and text runs as normal. For the
purposes of box generation and layout, the element must be treated as
if it had been replaced in the element tree by its contents (including
both its source-document children and its pseudo-elements, such as
::before and ::after pseudo-elements, which are generated before/after
the element’s children as normal).ref
Another idea is to adjust flex-grow and use border instead of relying on space-between:
.mainnav {
display: flex;
list-style-type: none;
margin: 0;
padding: 0;
border: 1px solid black;
}
.mainnav__item {
flex: 0 0 auto;
position: relative;
white-space: nowrap;
flex-grow:2; /*middle elements need to grow twice that edge element*/
text-align:center;
border-left:1px solid red;
border-right:1px solid red;
}
.mainnav__item a {
background:yellow;
}
.mainnav__item:first-child {
flex-grow:1;
text-align:left;
border-left:none;
}
.mainnav__item:last-child {
flex-grow:1;
text-align:right;
border-right:none;
}
<ul class="mainnav">
<li class="mainnav__item">
<a class="mainnav__link" href="#">Short</a>
</li>
<li class="mainnav__item">
<a class="mainnav__link" href="#">Looooooooooooooong</a>
</li>
<li class="mainnav__item">
<a class="mainnav__link" href="#">Mediuuum</a>
</li>
<li class="mainnav__item">
<a class="mainnav__link" href="#">whatever else</a>
</li>
</ul>
Related
This question already has answers here:
When flexbox items wrap in column mode, container does not grow its width
(9 answers)
Closed 1 year ago.
I am trying to create a flexbox container that contains a list of items in a row format and then each of those items will be a flexbox with items in a wrapping column format. However, it seems that the first containers rows do not expand to fit the contents of the wrapping columns and end up overlapping each other.
Demo
I want the end result to look like this:
.flex-group {
display: flex;
}
.flex-container {
padding: 0;
margin: 0;
list-style: none;
border: 1px solid silver;
display: flex;
flex-direction: column;
flex-wrap: wrap;
height: 500px;
}
.red li {
background: red;
}
.gold li {
background: gold;
}
.blue li {
background: deepskyblue;
}
.flex-item {
padding: 5px;
width: 100px;
height: 100px;
margin: 10px;
line-height: 100px;
color: white;
font-weight: bold;
font-size: 2em;
text-align: center;
}
<div class="flex-group">
<ul class="flex-container blue">
<li class="flex-item">1</li>
<li class="flex-item">2</li>
<li class="flex-item">3</li>
<li class="flex-item">4</li>
<li class="flex-item">5</li>
<li class="flex-item">6</li>
<li class="flex-item">7</li>
<li class="flex-item">8</li>
</ul>
<ul class="flex-container red">
<li class="flex-item">1</li>
<li class="flex-item">2</li>
<li class="flex-item">3</li>
</ul>
<ul class="flex-container gold">
<li class="flex-item">1</li>
<li class="flex-item">2</li>
<li class="flex-item">3</li>
<li class="flex-item">4</li>
<li class="flex-item">5</li>
</ul>
<div>
So at the end of the day, I'm looking to have a non-wrapping row where each of the child elements (dynamic amount) can contain a set of column wrapping elements (dynamic amount). Note: You can almost get the solution if you make the .flex-container have flex-direction: row but I need flex-direction: column since order does matter in this scenario. The main container needs to have a fixed height and each child container can have a dynamic width (due to wrapping elements causing them to grow horizontally).
If you will use grid, you can use this:
.flex-group {
display: flex;
}
.flex-container {
padding: 0;
margin: 0;
list-style: none;
border: 1px solid silver;
display: grid;
grid-template-rows: repeat(3, auto);
gap: 10px;
grid-auto-flow: column;
height: 500px;
}
.red li {
background: red;
}
.gold li {
background: gold;
}
.blue li {
background: deepskyblue;
}
.flex-item {
padding: 5px;
width: 100px;
height: 100px;
margin: 10px;
line-height: 100px;
color: white;
font-weight: bold;
font-size: 2em;
text-align: center;
}
<div class="flex-group">
<ul class="flex-container blue">
<li class="flex-item">1</li>
<li class="flex-item">2</li>
<li class="flex-item">3</li>
<li class="flex-item">4</li>
<li class="flex-item">5</li>
<li class="flex-item">6</li>
<li class="flex-item">7</li>
<li class="flex-item">8</li>
</ul>
<ul class="flex-container red">
<li class="flex-item">1</li>
<li class="flex-item">2</li>
<li class="flex-item">3</li>
</ul>
<ul class="flex-container gold">
<li class="flex-item">1</li>
<li class="flex-item">2</li>
<li class="flex-item">3</li>
<li class="flex-item">4</li>
<li class="flex-item">5</li>
</ul>
<div>
There are a lot of similar question but no answers. I think a workaround can satisfy your needs. Include a script to calculate the width depending on the height you assign to each flex-container and the flex-item height:
check it out here
I had to fix the height to 400px to get the same output as yours but this script will calculate number of cols and lines needed. Couldn't get the padding properly though so I set a variable that will get me the desired output
document.querySelectorAll('.flex-container').forEach((e)=>{
let p = 10 ;
let childCount = e.childElementCount;
let childHeight = e.children[0].clientHeight ;
let childWidth = e.children[0].clientWidth ;
let lines = Math.round((e.clientHeight / (e.children[0].clientHeight + 2*p))) ;
let cols = Math.round(childCount / lines) ;
let width = cols * ( 2*p + childWidth );
e.style.width = width+"px";
});
Still if you want a column based display for your items similar to Pinterest then you need to specify in advance the number of columns and use Masonry Layout :
.masonry-container {
column-count: 3;
column-gap: 15px;
}
.masonry-item {
display: inline-block;
width: 100%;
}
I'm not sure I got this, but it sounds like you don't want your columns to be in 'wrap' mode.
You may try to change those values:
.flex-container {
/* leave the rest of what was here*/
flex-wrap: nowrap;
height: 100%;
}
This is what I thought you want to achieve:
Here is my code https://jsfiddle.net/n4qpv91t/4/
HTML
<nav class="m-nav-main">
<ul class="m-nav-main__menu">
<li class="m-nav-main__item">
PLATFORM OF JOYSSS SAMPLETEXT
</li>
<li class="m-nav-main__item">
LOREM DIMTURAGUA
</li>
<li class="m-nav-main__item">
WORDS
</li>
<li class="m-nav-main__item">
OFFRES SPECIALS OF WORLD
</li>
<li class="m-nav-main__item">
LEARN FROM HOME
</li>
</ul>
</nav>
CSS
.m-nav-main {
width: 1000px;
}
.m-nav-main__menu {
list-style: none;
display: inline-flex;
justify-content: flex-end;
padding: 0;
margin: 0;
}
.m-nav-main__item {
display: inline-block;
border: solid 1px red;
}
.m-nav-main__link {
display: flex;
height: 50px;
padding: 0 1rem;
color: inherit;
border: solid 1px green;
align-items: center;
}
I have menu with text that breaks to 2nd line. Flexbox desnt get proper width of this text resulting in big empty spaces between text. I want those menu items to be as narrow as possible. I use flex on links to have centered vertically text. Changing it to inline or anything doesnt make a change.
Try adding flex: 1; to .m-nav-main__item.
I'm learning to build a nested drop-down menu using CSS.
I want to place down arrow at the end of each dropdown item. Like this:
Dropdown A[down arrow]
Nested dropdown B[down arrow]
Right now width and height of each list item differ according to the height and width of sub-list inside. This makes it difficult to place the down arrow just after the item name.
<div class="menu">
<ul>
<li>
Dropdown A
<ul>
<li class="link">
Im a link
</li>
<li class="link">
Im a link
</li>
<li>
Nested dropdown
<ul>
<li class="link">
Im a link
</li>
<li class="link">
Im a link
</li>
</ul>
</li>
</ul>
</li>
<li>
Dropdown B
<ul>
<li class="link">
Im a link
</li>
<li class="link">
Im a link
</li>
<li>
Nested dropdown
<ul>
<li class="link">
Im a link
</li>
<li class="link">
Im a link
</li>
<li>
Nested dropdown B
<ul>
<li class="link">
Im a link
</li>
<li class="link">
Im a link
</li>
<li>
Nested dropdown C
<ul>
<li class="link">
Im a link
</li>
<li class="link">
Im a link
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li class="link">Simple Link</li>
<li class="link">Another Link</li>
</ul>
</div>
// refer to: https://dev.to/felipperegazio/building-a-pure-css-menu-with-nested-dropdowns-hcn
.menu > ul
{
display: flex;
justify-content: space-evenly;
height: 40px;
li
{
position: relative;
background: lightgoldenrodyellow;
&::before
{
// draw down arrow
content: '';
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 5px solid #FFA500;
position: absolute;
// I WANT TO PLACE THIS RIGHT NEXT TO THE LIST ITEM NAME
}
&.link {
// links dont need arrow
&::before {
display: none;
}
}
ul
{
visibility: hidden;
opacity: 0;
}
&:hover > ul
{
visibility: visible;
opacity: 1;
}
}
}
ul
{
list-style: none;
}
You can see the live demo here: https://codepen.io/loganlee/pen/ExjYRyb?editors=1100
Try adding top and right position in &::before class.
&::before
{
content: '';
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 5px solid #FFA500;
position: absolute;
top: 18px;
right: 15px;
}
Really nice solution breakdown here: https://htmldog.com/techniques/dropdowns/
Replaced code with the code from above link. It is great!
All I had to do was
li {
float: left;
display: inline;
position: relative;
width: 150px;
list-style: none;
&::after
{
// draw down arrow
content: '';
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 5px solid #FFA500;
display: inline-block;
}
&.link {
// links dont need arrow
&::after {
display: none;
}
}
}
Make sure li::after { display: inline-block; }
As always, fixed demo here: https://codepen.io/loganlee/pen/ExjYRyb?editors=1100
I want to remove the dots in a ul but when I do, the height of the ul is reduced to 0px, so the contained li s have a transparent background. Can you give me an alternative ? This is the code :
<ul class="class_1" style="z-index: 1; top: -557.15px; left: 801px; display: block; position: relative; width: 200px;">
<li class="class_2">
<a>Ba</a>
</li>
<li class="class_2">
<a>Baa</a>
</li>
<li class="class_2">
<a>Baar</a>
</li>
</ul>
<style>
li {
list-style-type: none;
}
.class1
{
background-color: #FFFFFF;
border: 1px solid;
}
</style>
Thanks.
add css style for <li> elements - add display:block for them as well. that should do the trick, assuming there's no other style that is messing with this <ul>
I want a parent class (ul tag) to hold a bunch of li tags to that overlap each other, here is my code:
<ul class="overlap">
<li>
Overlap One
</li>
<li>
Overlap Two
</li>
<li>
Overlap Three
</li>
</ul>
I don't want them to be positioned absolutely, because I have a parent element that has a background border and color, but when I position the children absolutely, the parent doesn't stretch, here is the code in its entirety.
<blockquote>
<ul class="overlap">
<li>
Overlap One
</li>
<li>
Overlap Two
</li>
<li>
Overlap Three
</li>
</ul>
</blockquote>
The blockquote tag has a background color on to it, that is why the children cannot be positioned absolutely.
Thanks in advance for the help!
Like this? http://jsfiddle.net/2N8Jz/
<blockquote class="clearfix">
<ul class="overlap">
<li>Overlap One</li>
<li>Overlap Two</li>
<li>Overlap Three</li>
</ul>
</blockquote>
blockquote {
border: 1px solid black;
padding: 10px;
}
.overlap {
padding-left: 10px;
}
.overlap li {
border: 1px solid grey;
float: left;
margin: 0 0 0 -10px;
}
.clearfix:after {
content: ".";
display: block;
clear: both;
visibility: hidden;
line-height: 0;
height: 0;
}
.clearfix {
display: inline-block;
}
html[xmlns] .clearfix {
display: block;
}
* html .clearfix {
height: 1%;
}
Giving the <li>'s a negative margin should do the trick.
here's an example: http://jsfiddle.net/wvEms/1/