I'm using flexbox to create a basic layout for a web application. I want there to be a menu across the top and, below that, a primary content area on the left and a secondary content area on the right, both of which vertically fill the space left over below the menu. If just the content areas are included in the HTML, the stretch covers everything. When I include the menu, however, I end up with a lot of white space between the menu and content areas.
In the JS fiddle, I added a little JavaScript to remove the menu when you click on either of the links to give a better idea as to how much white space (1rem) I'd like between the menu and the two content areas.
Can this be achieved using flex? Thanks!
$(document).ready(function() {
$("a").click(function() {
$(this).closest(".main-menu").remove();
})
})
html {
height: 100%;
}
body {
display: flex;
min-height: 100%;
width: 100%;
margin: 0;
padding: 0;
border: 0;
flex-wrap: wrap;
align-content: stretch;
}
.main-menu {
width: 90%;
margin: 1rem calc(5% - 1px) 1rem calc(5% - 1px);
padding: 1rem;
border: 1px dashed black;
align-self: flex-start;
}
.main-menu ul {
margin: 0;
border: 0;
padding: 0;
display: inline;
}
.main-menu ul li {
margin: 0;
border: 0;
padding: 0;
display: inline;
}
.primary-stuff {
margin: 1rem 1rem 1rem calc(5% - 1px);
width: calc(75% - 1rem - 1px);
border: 1px dashed black;
}
.secondary-stuff {
margin: 1rem calc(5% - 1px) 1rem 1rem;
width: calc(15% - 1rem - 1px);
border: 1px dashed black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="main-menu">
<ul>
<li><a>Link 1</a></li>
<li><a>Link 2</a></li>
</ul>
</div>
<div class="primary-stuff"></div>
<div class="secondary-stuff"> </div>
<div style="position:absolute;background:#FFC; width: calc(5% - 1px); height: 100%; left: 0;"></div>
<div style="position:absolute;background:#FFC; width: calc(5% - 1px); height: 100%; right: 0; top: 0;"></div>
<div style="position:absolute;background:#FFC; height: 1rem; width: 100%; right: 0; top: 0;"></div>
<div style="position:absolute;background:#FFC; height: 1rem; width: 100%; right: 0; bottom: 0;"></div>
Fiddle:
https://jsfiddle.net/don01001100/kdg04ubr/
You can't really do this with flexbox because of the way align-content works with wrap.
However, the layout is simple with CSS Grid layout:
body {
display: grid;
grid-template-columns: 75% 1fr;
grid-template-rows: auto 1fr;
grid-gap: 1rem;
min-height: 100vh;
}
nav {
grid-column: 1 / -1;
}
/* non-essential decorative styles */
body { margin: 0; padding: 1rem 2rem; }
nav { background-color: lightgreen; padding: 1rem; }
.primary-stuff { background-color: lightblue; }
.secondary-stuff { background-color: orange; }
* { box-sizing: border-box; }
<nav>
<a>Link 1</a>
<a>Link 2</a>
</nav>
<div class="primary-stuff"></div>
<div class="secondary-stuff"> </div>
Also, all those calculations you have for margins are not necessary. Here's an easy solution that will simplify your code: Flexbox: 4 items per row
You might find that the Grid Layout is a better fit for this type of layout, as it provides a cleaner method of arranging elements in the way you require, without the need for additional HTML markup.
You could achieve the layout you require using CSS-grid as follows:
html {
height: 100%;
}
body {
/* Cause grid to fill vertical space */
height:100%;
/* Prevent overflow due to padding */
box-sizing:border-box;
/* Use grid display type */
display: grid;
/* Tells grid to cause second row to fill
avaible/remaining vertical space */
grid-template-rows: auto 1fr;
/* Define the grid layout, in terms of areas
that are distributed between 3 colums and
2 rows */
grid-template-areas:
"menu menu menu"
"primary primary secondary";
/* Specify spacing between grid elements */
grid-gap: 1rem;
margin: 0;
padding: 1rem;
background:grey;
}
.main-menu {
background: pink;
/* Accociate the main-menu with the menu area
of your grid-template-areas defined above */
grid-area: menu;
}
.main-menu ul {
margin: 0;
padding: 0;
display: inline;
}
.main-menu ul li {
margin: 0;
padding: 0;
display: inline;
}
.primary-stuff {
background: lightblue;
/* Accociate the primary-stuff with the primary area
of your grid-template-areas defined above */
grid-area: primary;
}
.secondary-stuff {
background: lightgreen;
/* Accociate the secondary-stuff with the secondary area
of your grid-template-areas defined above */
grid-area: secondary;
}
<div class="main-menu">
<ul>
<li><a>Link 1</a></li>
<li><a>Link 2</a></li>
</ul>
</div>
<div class="primary-stuff">
primary content
</div>
<div class="secondary-stuff">
secondary content
</div>
Related
I'm trying to use grid layout to create two columns. The right column has a fixed width but the left one should take whatever's left out. However, the problem is that when the content is too long, it extends the left column and causes a horizontal scrollbar in the container.
Now I can achieve this with other methods like calc but I'm trying to learn grid layout.
Here's a fiddle to demonstrate: https://jsfiddle.net/pta2c7um/
Ideally I would want the long title to get truncated respecting the grid structure.
Solution:
#ticket-viewer .list li{
/* grid-template-columns: auto 80px; */
grid-template-columns: minmax(0, 1fr) 80px;
padding: 1rem;
cursor: pointer;
}
Working example:
.list{
margin: 0;
padding: 0;
}
.list li {
padding: 0.8rem 0;
position: relative;
}
.list li small {
color: #777777;
}
.list li .content {
padding: 0 !important;
}
.list li .right-assist {
text-align: right;
}
.list.left-assist li, .list.right-assist li {
display: grid;
}
.list.right-assist li {
grid-template-columns: auto 40px;
}
.list.dividers li:before {
content: "";
position: absolute;
height: 1px;
background: #CCC;
bottom: 0px;
left: 0;
right: 0;
}
#ticket-viewer{
border: 1px solid #CCC;
display: flex;
}
#ticket-viewer .list{
width: 40%;
height: 560px;
overflow-y: auto;
border-right: 1px solid #CCC;
}
#ticket-viewer .list li{
/* grid-template-columns: auto 80px; */
grid-template-columns: minmax(0, 1fr) 80px;
padding: 1rem;
cursor: pointer;
}
.truncate {
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
<div id="ticket-viewer">
<ul class="list right-assist dividers">
<li data-id="1" class="">
<div class="content"><span class="truncate">Test</span><small>Bug/Error on Website</small></div>
<div class="right-assist">Resolved</div>
</li>
<li data-id="2" class="active">
<div class="content"><span class="truncate">This is a very very long subject</span><small>Feature Request</small></div>
<div class="right-assist">Resolved</div>
</li>
</ul>
</div>
Explanation:
grid-template-columns: minmax(0, 1fr) 80px; step-by-step explanation:
Define two columns.
The last has fixed 80px width;
The first occupies remaining space.
minmax(0, ..) is used to tell browser to shrink width of column, if its content is wider than 1fr (i.e. 1 fraction of remaining space).
More info on MDN
There is a mysterious gap at the bottom of the ".one" column.
I gathered that this results from requesting no scroll bars.
Yet somehow the vertical scrollbar disappears entirely, but the horizontal scroll disappears while leaving a gap in its place.
What is this gap and how do I get rid of it?
d3.select('.one')
.selectAll('div')
.data(d3.range(40))
.enter()
.append('div')
.attr('class', 'picture box')
.append('h2')
.text(d => d);
html, body {
width: 100%; height: 100%; margin: 0;
}
.container {
width: 100%; height: 100%;
display: grid;
grid-template-columns: 10%;
}
.box {
background-color: #484848;
color: #fff;
border-radius: 5px;
padding: 1px; margin: 1px;
}
.menu {
text-align: center;
overflow: scroll;
}
.menu::-webkit-scrollbar {
width: 0 !important;
}
.one { grid-column: 1; grid-row: 1; }
.two { grid-column: 2; grid-row: 1; }
div.picture {
box-sizing: content-box;
max-width: 100%;
border: 2px solid gray;
border-radius: 5px;
background-color: #222;
display: flex;
justify-content: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="container">
<div class="box one menu noscrollbar">
<h2>One</h2>
</div>
<div class="box two menu noscrollbar">
<h2>Two</h2>
</div>
</div>
It's because when you allow it to scroll, it's leaving a place for the horizontal scrollbar. Tell it to only scroll on the y-axis (up and down) with overflow-y: scroll in the css.
From Mozilla:
Content is clipped if necessary to fit the padding box. Browsers always display scrollbars whether or not any content is actually clipped, preventing scrollbars from appearing or disappearing as content changes. Printers may still print overflowing content.
Full page explainer on overflow: https://developer.mozilla.org/en-US/docs/Web/CSS/overflow
See solution below:
d3.select('.one')
.selectAll('div')
.data(d3.range(40))
.enter()
.append('div')
.attr('class', 'picture box')
.append('h2')
.text(d => d);
html, body {
width: 100%; height: 100%; margin: 0;
}
.container {
width: 100%; height: 100%;
display: grid;
grid-template-columns: 10%;
}
.box {
background-color: #484848;
color: #fff;
border-radius: 5px;
padding: 1px; margin: 1px;
}
.menu {
text-align: center;
overflow-y: scroll;
}
.menu::-webkit-scrollbar {
width: 0 !important;
}
.one { grid-column: 1; grid-row: 1; }
.two { grid-column: 2; grid-row: 1; }
div.picture {
box-sizing: content-box;
max-width: 100%;
border: 2px solid gray;
border-radius: 5px;
background-color: #222;
display: flex;
justify-content: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="container">
<div class="box one menu noscrollbar">
<h2>One</h2>
</div>
<div class="box two menu noscrollbar">
<h2>Two</h2>
</div>
</div>
It would help to know which browser you’re seeing this problem in. In Firefox on a Mac with hidden scrollbars there is no gap at the bottom of the .one column.
However, you could try overflow-y: scroll on .menu, instead of overflow as this will only scroll the container along the y-axis. overflow-x is, of course, the companion.
I've been pulling my hairs over a CSS issue that I will try to describe here:
In the following example (https://codesandbox.io/s/jjq4km89y5), you can see a scrollable content (purple background), and a "tooltip" (always showing in this example for practical reasons, red background) that is half hidden by the left panel.
What I need is for both the purple content to be scrollable, AND for the red tooltip to show:
The CSS uses CSS Grid, but the problem is the same if I use flex instead.
The problem seems to lies on the overflow: auto statement, (line 59 of styles.css in the code sandbox).
Thanks!!
(to see the example live, please go to https://codesandbox.io/s/jjq4km89y5)
The code, otherwise, can be seen here:
<div class="page">
<div class="menu">Menu</div>
<div class="content">
<div class="grid">
<div class="nav">Top Nav</div>
<div class="panel">Left Panel</div>
<div class="analysis">
<div>
<p>Some random content</p>
<div class="tooltip-trigger">
A div with a Tooltip (always showing here)
<div class="tooltip">
You should be able to see the entirety of this text here,
going over the Left Nav
</div>
</div>
<div class="long-content">
Some very long content that should make the purple div scroll
</div>
</div>
</div>
</div>
</div>
</div>
And the CSS:
.page {
display: flex;
margin: 0;
height: 100%;
width: 100%;
position: fixed;
}
.menu {
width: 40px;
background-color: orange;
height: 100%;
}
.content {
flex: 1;
}
.grid {
display: grid;
grid-template-columns: 210px auto;
grid-template-rows: 60px auto;
grid-template-areas:
"nav nav"
"panel analysis";
height: 100%;
}
.nav {
grid-area: nav;
padding: 10px 40px;
display: flex;
justify-content: space-between;
align-items: center;
background-color: grey;
border-bottom: 3px solid black;
}
.panel {
grid-area: panel;
border-right: solid 3px black;
background-color: grey;
}
.panel > div {
height: calc(100vh - 60px);
}
.analysis {
grid-area: analysis;
padding: 60px;
height: calc(100vh - 60px);
background-color: purple;
/* The problem is here:
if set to "auto", then we have a scrollbar but the red tooltip is not visible
If set to "visible", we get the red tooltip but the scroll is gone
*/
overflow: auto;
}
.tooltip-trigger {
position: relative;
background-color: green;
border: 5px dashed rebeccapurple;
}
.tooltip {
position: absolute;
border: 5px dashed orange;
background-color: red;
height: 200px;
width: 200px;
top: 10px;
left: -200px;
}
.long-content {
height: 3000px;
background-color: pink;
border: 5px dashed darkred;
}
You can also see the real-world app and what it does:
The tooltip as you can see will display for all these cells in the table, and needs to be precisely attached to that cell.
The content where the table is needs to be scrollable as well.
This may be a solution, or just a nudge in the right direction.
Instead of the tooltip being absolutely-positioned relative to the parent element, make it relative to a more distant ancestor, so it's not affected by the overflow of the purple div.
So, instead of this:
.grid {
display: grid;
grid-template-columns: 210px auto;
grid-template-rows: 60px auto;
grid-template-areas:
"nav nav"
"panel analysis";
height: 100%;
}
.tooltip-trigger {
position: relative;
background-color: green;
border: 5px dashed rebeccapurple;
}
.tooltip {
position: absolute;
border: 5px dashed orange;
background-color: red;
height: 200px;
width: 200px;
top: 10px;
left: -200px;
}
Try something along these lines:
.grid {
position: relative; /* new */
display: grid;
grid-template-columns: 210px auto;
grid-template-rows: 60px auto;
grid-template-areas:
"nav nav"
"panel analysis";
height: 100%;
position: relative;
}
.tooltip-trigger {
/* position: relative; */
background-color: green;
border: 5px dashed rebeccapurple;
}
.tooltip {
position: absolute;
border: 5px dashed orange;
background-color: red;
height: 200px;
width: 200px;
top: 200px; /* adjusted */
left: 60px; /* adjusted */
}
revised demo
This may be a bit of a hack, but I think it can be made to work without too bad side effects: Instead of arranging the left panel and analysis as sibling DOM-nodes, you could layer analysis inside and over left panel.
With some tweaking to adjust placement of content you could make it look like they are side by side. Instead of scrolling analysis, with a static left panel, you can scroll left panel and make the left panel content absolutely positioned.
I made a quick and dirty implementation to demonstrate the mechanics:
Revised code example
Markup:
<div className="panel">
<div className="panelContent">Left Panel</div>
<div className="panelSpacer" />
<div className="analysis">
<div>
<p>Some random content</p>
<div className="tooltip-trigger">
A div with a Tooltip (always showing here)
<div className="tooltip">
You should be able to see the entirety of this text here,
going over the Left Nav
</div>
</div>
<div className="long-content">
Some very long content that should make the purple div scroll
</div>
</div>
</div>
</div>
CSS:
.panel {
grid-area: panel;
border-right: solid 3px black;
background-color: gray;
display: flex;
justify-content: space-between;
overflow: auto;
}
.panelSpacer {
width: 210px;
position: relative;
top: 0;
}
.panelContent {
width: 210px;
position: absolute;
top: 60px;
}
.panel > div {
height: calc(100vh - 60px);
}
.analysis {
position: relative;
width: calc(100% - 210px);
padding: 60px;
height: 100%;
background-color: purple;
/* The problem is here:
if set to "auto", then we have a scrollbar but the red tooltip is not visible
If set to "visible", we get the red tooltip but the scroll is gone
*/
}
I´m trying to align two navigations inside a sidebar –with 100% viewport height – by use of flexbox.
the red box should be placed on the top of it´s sidebar parent
the blue box on the bottom.
In case the red navigation grows and the space between both is to little the sidebar should be scrollable in y-axis. What I´ve tried is setting top and bottom margin for both without luck. Can somebody help me out ?
Thanks!
html, body {
margin: 0;
}
* {
box-sizing: border-box;
}
.sidebar {
height: 100vh;
width: 300px;
background: #ccc;
padding: 10px;
overflow-y: scroll;
}
.sidebar__top {
background: red;
height: 200px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.sidebar__bottom {
background: blue;
height: 100px;
margin-top: auto;
}
<aside class="sidebar">
<nav class="sidebar__top"></nav>
<nav class="sidebar__bottom"></nav>
</aside>
Here is my fiddle: http://jsfiddle.net/1dw7h2sp/1/
There are probably other ways to do this. In short I did the following:
Wrap your elements with a parent that is able to grow in size (.sidebar__wrapper)
Set the min-height instead of height so it can grow
Use flex-grow if you want an element to fill out the remaining space.
html, body {
margin: 0;
}
* {
box-sizing: border-box;
}
.sidebar {
height: 100vh;
width: 300px;
background: #ccc;
overflow-y: scroll;
margin: 0;
padding: 0;
}
/* set up a wrapper that can grow in size */
.sidebar__wrapper {
height: auto;
min-height: 100vh;
width: 100%;
background: #808080;
padding: 10px;
margin: 0;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.sidebar__top {
background: red;
min-height: 200px;
margin-bottom: 10px;
/* this fills up the remaining space */
flex-grow: 1;
}
.sidebar__bottom {
background: blue;
min-height: 100px;
}
<aside class="sidebar">
<div class="sidebar__wrapper">
<nav class="sidebar__top" contenteditable="true">
<p>test</p>
</nav>
<nav class="sidebar__bottom"></nav>
</div>
</aside>
I would like to center an element vertically and horizontally. The catch is that any sibling elements should maintain their position relative to the centered element. If the siblings are large enough, they may overflow the viewport. The siblings have variable heights.
I've started a code sample here: https://jsfiddle.net/hqmoz9xy/2/
html,
body {
height: 100%;
}
body {
margin: 0;
padding: 1em;
}
body,
.container {
box-sizing: border-box;
}
.container {
border: 1px solid red;
padding: 1em;
width: 100%;
height: 100%;
}
.main-display {
width: 100px;
height: 100px;
background-color: #999;
padding: 1em;
}
<div class="container">
<div class="main-display">
Main box: this box should be at the center of the container.
</div>
<ul class="extra-info">
<li>These items should naturally follow the main box and not care about vertical centering.</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>
This is easily accomplished using JS and negative margins but I would like to do it only with CSS. Is there a way to do this using flex?
You can use flexbox:
.container {
display: flex; /* Magic begins */
flex-direction: column;
}
.before, .after {
flex: 1; /* If possible, center .main vertically */
min-height: 0; /* Really, don't care about overflow, just center .main vertically */
}
.main {
align-self: center; /* Center .main horizontally */
}
html,
body {
height: 100%;
}
body {
margin: 0;
padding: 1em;
}
body,
.container {
box-sizing: border-box;
}
.container {
border: 1px solid red;
padding: 1em;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.before,
.after {
flex: 1;
min-height: 0;
}
.main {
width: 100px;
height: 100px;
background-color: #999;
padding: 1em;
align-self: center;
}
<div class="container">
<div class="before"></div>
<div class="main">
Main box: this box should be at the center of the container.
</div>
<div class="after">
<ul class="extra-info">
<li>These items should naturally follow the main box and not care about vertical centering.</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>
</div>
setting negative margin to virtually reduce width down to zero for extra-info, using display:table to shrink on content and and center, you could do something like this :
html,
{
height: 100%;
}
body {
min-height:100%;
border: 1px solid red;
}
body,
.container {
box-sizing: border-box;
}
.container {
padding: 1em;
display: table;
margin: auto;
}
.main-display {
width: 100px;
height: 100px;
background-color: #999;
padding: 1em;
}
.extra-info {
padding:0;
margin:0;
background:lightgray;
margin-right: -50vw;
max-width: 50vw
/* size virtually reduce to zero with equal negative margin value*/
}
<div class="container">
<div class="main-display">
Main box: this box should be at the center of the container.
</div>
<ul class="extra-info">
<li>These items should naturally follow the main box and not care about vertical centering.</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>
I am not sure I quite understand what do you want but maybe ?
#main {
width: 100%;
height: 100px;
border: 0px;
display: -webkit-flex;
display: flex;
}
#main div {
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
}
<div id="main">
<div style="background-color:white;">empty</div>
<div style="background-color:Blue;">Your box here</div>
<div style="background-color:white;">empty</div>
</div>
You also can do it with margin only by setting left margin to 40% or 35% depending on your box width