Sizing a positioned element based on one child only - html

I have an absolutely-positioned container with two children. I would like the width of the container to be based on the width of the first child, while the second child dynamically adapts to the width of its parent.
const passenger = document.getElementById("passenger");
function toggle(){
passenger.classList.toggle("squashed");
}
#top {
padding: 4px;
border: thin solid red;
position: absolute;
}
ul {
padding: 4px;
border: thin solid green;
}
li {
padding: 4px;
border: thin solid purple;
}
ul#passenger li {
width: auto; /* ??? No good -- what goes here ??? */
}
.squashed {
max-width: 10em;
}
<button onclick="toggle()">Toggle</button>
<hr/>
<div id="top">
<ul id="driver">
<li>This is the desired width</li>
</ul>
<ul id="passenger">
<li>This should be forced to wrap because its width doesn't contribute to the container width calculation.</li>
</ul>
</div>
Click the "Toggle" button in the example to see an approximation of the effect I'm going for. When I don't artificially constrain the width of passenger, it grows horizontally to fill the page, which pushes the width of top to match it.
I have tried setting #passenger li {width:auto;} as well as width:100%;, and also various combinations of word-wrap, etc, but I can't figure out how to force the li to take on the width of its parent. It seems that #passenger li {width:100%;} only works when the width of top is explicitly set, rather than based on the width of driver. It also "works" the way I want if I take passenger completely out of the flow by making it position: absolute but I want top to completely contain both elements, so this is not an option.
Is the desired effect possible using CSS only (no JS)?

You could do this with display: table on parent element and some small width, for example 1%, and then you also set white-space: nowrap on first ul element.
In this case all of the other child elements will adjust its width to the largest one.
#top {
padding: 4px;
border: thin solid red;
position: absolute;
display: table;
width: 1%;
}
#driver {
white-space: nowrap;
}
ul {
padding: 4px;
border: thin solid green;
}
li {
padding: 4px;
border: thin solid purple;
}
<div id="top">
<ul id="driver">
<li>This is the desired width</li>
</ul>
<ul id="passenger">
<li>This should be forced to wrap because its width doesn't contribute to the container width calculation.</li>
</ul>
</div>

Related

CSS - Calc inaccuracy

I have a ul element and 5 child <li>.
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
The <ul> has a display: flex property.
I tried using the calc property on the <li> to evenly size my list items:
calc:(100% / 5);
This gives the desired result and evenly sizes the 5 <li> blocks
Now I added borders to the right side of all, but the last child <li> element. So I reduced the total width of all the borders combined from the total width of the <ul>.
calc:((100% - 8px) / 5);
This also worked properly and evenly sized the <li> blocks with the borders.
ul {
width: 600px;
height: 300px;
margin: 0;
padding: 0;
display: flex;
background: red;
}
li {
list-style: none;
display: block;
width: calc((100% - 0.8px) / 5);
height: 100%;
border-right: 0.2px solid black;
background: blue;
}
li:last-child {
border-right: none;
}
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
Now I tried to set the border width in viewport unit vw instead of px but it gives a different result.
ul {
width: 600px;
height: 300px;
margin: 0;
padding: 0;
display: flex;
background: red;
}
li {
list-style: none;
display: block;
width: calc((100% - 0.8vw) / 5);
height: 100%;
border-right: 0.2vw solid black;
background: blue;
}
li:last-child {
border-right: none;
}
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
As you can see from the snippet above, there is a little bit of space to the right. This becomes bigger with a wider viewport (try viewing the snippet full page). So I think the problem lies with the vw units and flexbox here.
So what is the cause of this error?
EDIT:
From the provided answers and comments, I have seen there are other and more proper approches to achieve what I was trying to do. I appreciate those answers but those are not the answers to my question. Since calc is showing an error in this case, most likely it will cause more problems when I try to use calc and viewport units in other cases (not just borders). So I need to know the reason and "calc" fix.
You don't need to do calc to add inner content for your li. If you give the box-sizing: border-box; prop, border and padding will not make the container grows.
ul {
width: 600px;
height: 300px;
margin: 0;
padding: 0;
display: flex;
background: red;
}
li {
list-style: none;
display: block;
width: calc(100% / 5);
height: 100%;
border-right: 2px solid black;
background: blue;
box-sizing: border-box;
}
li:last-child {
border-right: none;
}
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
It seems you are using flex to distribute your lis inside of your ul. In this case, you do not need to calculate the widths to make those equal. That is what flex is supposed to do.
See this example:
* { box-sizing: border-box; margin: 0; padding: 0; }
ul, li { list-style: none; }
ul { width: 600px; height: 300px; background: red; display: flex; }
li {
flex: 1 0 auto;
border-right: 0.8vw solid black;
background: blue;
}
li:last-child { border-right: none; }
<ul>
<li></li><li></li><li></li><li></li><li></li>
</ul>
When specifying flex dsiplay on containers, you just need to apply the flex property on the children to make them behave as flexible items. In your specific use-case, you need the following:
flex-grow: set to 1. This will allow your li to grow to fill the entire ul width.
flex-shrink: set to 0. You do not want your li to shrink.
flex-basis: set to auto. You want your li to grow to an equalized width automatically.
So, you see, you do not need calc at all.
You also, need not to specifically apply display: block to your lis. As per the specs here: https://www.w3.org/TR/css-flexbox-1/#flex-items
The display value of a flex item is blockified: if the specified
display of an in-flow child of an element generating a flex container
is an inline-level value, it computes to its block-level equivalent
As pointed out by #BoltClock in the comments, the list-item is already blockified.
Borders
Now, coming to borders. In your first example you have specified the width in 0.2px unit. This makes no sense. Although the specs allow fractional pixels, it will round it off to the nearest pixel available on display which is usually 1.
In you second example, you want the borders to scale with the viewport. That's good. No problem with the 0.8vw.
Just see the example I posted, in this fiddle. Resize the result window and you will see the borders changing relative to the viewport size.
Lastly, you may or may not want to set the box-sizing. It depends on your layout. Whatever you use, you use it consistently.
Pixels (px): Absolute pixels. So for example, 20px will be literally 20 pixels on any screen. If a monitor is of 1980x1200, and you set an element's height to 200px, the element will take 200 pixels out of that.
Viewport height/width (vw/vh): Size relative to the viewport (browser windoe, basically).
1px = (100 / document.documentElement.clientWidth)vw
1px = (100 / 500) = 0.2vw

Understanding CSS table-cell and percentage width

I was checking how Github display the below menu:
If you notice, each menu item is given an equal width. In CSS, we should give it any percentage value, what's the reason behind that? notice that the parent div is not given display: table property.
div {
border: 1px solid #000;
border-radius: 4px;
}
div ul {
padding: 0;
margin: 0;
}
div ul li {
display: table-cell;
width: 1%;
text-align: center;
border-right: 1px solid #000;
padding: 10px 0;
}
div ul li:last-child {
border-right: 0;
}
<div>
<ul>
<li>Commits
</li>
<li>Branch
</li>
<li>Contribution
</li>
<li>anything
</li>
</ul>
</div>
What's the reason behind the percentage width?
This has to do with how automatic table layout works. In particular:
A percentage value for a column width is relative to the table width. If the table has 'width: auto', a percentage represents a constraint on the column's width, which a UA should try to satisfy. (Obviously, this is not always possible: if the column's width is '110%', the constraint cannot be satisfied.)
In your case, you're setting a minuscule percentage width on every table-cell element. But the browser needs to ensure that the table-cells fill up the width of the table (which itself is as wide as its containing block), so it has to expand the cells to occupy as much space within the table as possible.
The reason why this results in approximately equal-width cells is because the percentage value is equal for all of them. If, for example, you set a slightly larger width for one of the cells, you'll see that it grows wider and the other cells become narrower to accommodate:
div {
border: 1px solid #000;
border-radius: 4px;
}
div ul {
padding: 0;
margin: 0;
}
div ul li {
display: table-cell;
width: 1%;
text-align: center;
border-right: 1px solid #000;
padding: 10px 0;
}
div ul li:first-child {
width: 3%;
}
div ul li:last-child {
border-right: 0;
}
<div>
<ul>
<li>Commits
</li>
<li>Branch
</li>
<li>Contribution
</li>
<li>anything
</li>
</ul>
</div>
However, note that there is a slight difference because some cells have longer content than others, and the length of this content is accounted for when calculating the cell widths.
The reason why a value as small as 1% is used is so that you can continue to add cells and the browser will still try to distribute the available width as evenly as possible. You're essentially telling the cells that they don't need to be a certain width at minimum so you can add away (although technically, 1% is still something).
The fact that no elements are assigned display: table is inconsequential, because an anonymous table wrapper will be generated around the table-cells so that they can render correctly. This anonymous table wrapper functions exactly the same as an unstyled element with display: table, which means the table width is auto for the purposes of calculating percentage cell widths.
This is something called the 1% width table hack.
This is because the table-cell inherits it's width from the parent div, allowing you to specify a related percentage of the cell width.
Here is a working demo on CodePen that you can play with and examine further.
http://codepen.io/ld0rman/pen/FCiLh
<ul>
<li>One</li>
<li>Two</li>
<li>Three</li>
<li>Four</li>
<li>Five</li>
</ul>
CSS:
ul {
list-style-type: none;
margin: 100px auto;
width: 80%;
}
li {
display: table-cell;
width: 1%;
text-align: center;
}
a {
display: block;
border: 1px solid black;
padding: 25px;
text-decoration: none;
color: white;
text-transform: uppercase;
font-weight: bold;
background: grey;
&:hover {
text-decoration: none;
color: yellow;
background: darken(grey, 10%);
}
}
As you can see, it is coded similarly to your github example. Any more questions, ask away!

HTML & CSS: How to create four equal size tabs that fill 100% width?

I am trying to create a navigation panel for my website. I would like it to consist of:
Four tabs in equal size with text-centered in each tab.
They should fill the whole page width.
I would really like the design to be flexible and browser friendly. I have tried various float techniques, but I can't get it to work. I hope that you can help me out!
Thank you.
HTML
EDIT: it's 2015 and HTML5 has been there for a while; following code should be inside a nav element (html5doctor) with landmark ARIA attribute role="navigation" on it (and 99.9% of the time be unique in any given page).
A navigation panel should use an unordered list of links:
<ul id="nav">
<li>One</li>
<li> Second</li>
<li>Third</li>
<li>Fourth and last, so large that... worst case</li>
</ul>
CSS
EDIT2: It's 2017, just use Flexbox 😲 (with or without flex-wrap: wrap)
inline-block is useful but has one drawback: whitespace between two elements must be carefully managed. Whether removed or no </li> in HTML5 or </li> at the beginning of the following line stuck like </li><li>next item or other tricks, you still have to do something or it'll create a ~4px gap between 2 elements.
25% + 25% + 25% + 25% doesn't equal 100% on all browsers if the total isn't a multiple of 4. Each browser has its own rounding method.
If you want elements to total 100% width and equal width, another method is to use display: table (and table-cell) with table-layout: fixed to force browsers to use the other table algorithm, the one that doesn't try to adapt cells width to content but respect the widths wanted by the designer/developer as far as possible.
ul {
margin: 0;
padding: 0;
}
li {
list-style-type: none;
}
#nav {
display: table;
table-layout: fixed;
text-align: center;
}
#nav li {
display: table-cell;
width: 25%;
padding-right: 1px;
height: auto;
vertical-align: bottom;
}
#nav a {
display: block;
min-height: 100%;
padding: 4px 10px;
background-color: #222;
color: white;
border-radius: 6px 6px 0 0;
}
Fiddle
http://jsfiddle.net/PhilippeVay/aHCy3/1/
edit: http://jsfiddle.net/PhilippeVay/aHCy3/2/ with another method for space between each tab, courtesy of my colleague.
You don't need floats for this. Just set the width to 25%, or a tiny bit less than 25%. If you're using this on a block level element, set display: inline-block. This will work for all browser sizes, as well as respond to window resize.
HTML
<div class="nav">Nav 1</div>
<div class="nav">Nav 2</div>
<div class="nav">Nav 3</div>
<div class="nav">Nav 4</div>​
CSS
body, html {
width: 100%;
padding: 0;
margin: 0;
}
.nav {
width: 24%; /*Slightly less than 1/4th of the width*/
display: inline-block;
padding: 0;
margin: 0;
text-align: center;
}​​
Live demo
css:
.tab {
float: left;
width:25%;
height:25px;
background:black;
border:1px solid #fff;
box-sizing: border-box;
}​
html:
<div class="tab"></div>
<div class="tab"></div>
<div class="tab"></div>
<div class="tab"></div>​
jsfiddle: http://jsfiddle.net/zP7Xh/6/

Border Height on CSS

I have a table TD and on the right of it I want to add a 1 pixel border, so I've done this:
table td {
border-right:1px solid #000;
}
It works fine but the problem is that the border's height takes the total TD's height.
Is there a way to set the height of the border?
I have another possibility. This is of course a "newer" technique, but for my projects works sufficient.
It only works if you need one or two borders. I've never done it with 4 borders... and to be honest, I don't know the answer for that yet.
.your-item {
position: relative;
}
.your-item:after {
content: '';
height: 100%; //You can change this if you want smaller/bigger borders
width: 1px;
position: absolute;
right: 0;
top: 0; // If you want to set a smaller height and center it, change this value
background-color: #000000; // The color of your border
}
No, there isn't. The border will always be as tall as the element.
You can achieve the same effect by wrapping the contents of the cell in a <span>, and applying height/border styles to that. Or by drawing a short vertical line in an 1 pixel wide PNG which is the correct height, and applying it as a background to the cell:
background:url(line.png) bottom right no-repeat;
Yes, you can set the line height after defining the border like this:
border-right: 1px solid;
line-height: 10px;
For td elements line-height will successfully allow you to resize the border-height as SPrince mentioned.
For other elements such as list items, you can control the border height with line-height and the height of the actual element with margin-top and margin-bottom.
Here is a working example of both:
http://jsfiddle.net/byronj/gLcqu6mg/
An example with list items:
li {
list-style: none;
padding: 0 10px;
display: inline-block;
border-right: 1px solid #000;
line-height: 5px;
margin: 20px 0;
}
<ul>
<li>cats</li>
<li>dogs</li>
<li>birds</li>
<li>swine!</li>
</ul>
Building on top of #ReBa's answer above, this custom-border class is what worked for me.
Mods:
working with border instead of backaground-color since background-color is not consistent.
Setting height & top of the properties of :after in such a way that the total comes up to 100% where bottom's value is implicit.
ul {
list-style-type: none;
display: flex;
flex-direction: row;
}
li {
padding: 10px;
}
.custom-border {
position: relative;
}
.custom-border:after {
content: " ";
position: absolute;
border-left: 1px #6c757d solid;
top: 35%;
right: 0;
height: 30%;
margin-top: auto;
margin-bottom: auto;
}
<ul>
<li class="custom-border">
Hello
</li>
<li class="custom-border">
World
</li>
<li class="custom-border">
Foo
</li>
<li class="custom-border">Bar</li>
<li class="custom-border">Baz</li>
</ul>
Good Luck...
No, you cannot set the border height.
This will add a centered border to the left of the cell that is 80% the height of the cell. You can reference the full border-image documentation here.
table td {
border-image: linear-gradient(transparent 10%, blue 10% 90%, transparent 90%) 0 0 0 1 / 3px;
}
Just like everyone else said, you can't control border height.
But there are workarounds, here's what I do:
table {
position: relative;
}
table::before { /* ::after works too */
content: "";
position: absolute;
right: 0; /* Change direction for a different side*/
z-index: 100;
width: 3px; /* Thickness */
height: 10px;
background: #555; /* Color */
}
You can set height to inherit for the height of the table or calc(inherit - 2px) for a 2px smaller border.
Remember, inherit has no effect when the table height isn't set.
Use height: 50% for half a border.
Demo
table {
border-spacing: 10px 0px;
}
.rightborder {
border-right: 1px solid #fff;
}
Then with your code you can:
<td class="rightborder">whatever</td>
Hope that helps!
Currently, no, not without resorting to trickery. borders on elements are supposed to run the entire length of whatever side of the element box they apply to.
.main-box{
border: solid 10px;
}
.sub-box{
border-right: 1px solid;
}
//draws a line on right side of the box.
later add a margin-top and margin-bottom.
i.e.,
.sub-box{
border-right: 1px solid;
margin-top: 10px;;
margin-bottom: 10px;
}
This might help in drawing a line on the right-side of the box with a gap on top and bottom.
table td {
border-right:1px solid #000;
height: 100%;
}
Just you add height under the border property.

Ensure line between two divs in larger div extends all the way to the bottom of the parent div

I have two divs placed inside a larger div. Each one of these two divs contains dynamically generated content and thus their heights vary, so I cannot know which one of the two will be taller. The parent div they are placed in has a 1px border and I would like to have 1px line between these divs as well, so that the line extends all the way down to the bottom of the parent div which adjusts itself based on the heights of the child divs. This is much easier to understand in the following picture:
I have tried setting the child divs to a height of 100%, but that does not seem to be working. How can I accomplish this effect? (This also needs to work in IE6)
Well, this is relatively easy, if all you want is a single border extending to the full height of the tallest element (in this case the tallest div), albeit my solution doesn't really address the potential equal heights issue (if you wanted the background-color of each div to extend to the full-height of the tallest element. It does, though, satisfy your request for the full-height single border:
#left,
#right {
width: 40%; /* adjust to taste */
float: left;
padding: 1em; /* adjust to taste */
}
#left {
border-right: 4px solid #000; /* adjust to taste */
}
#right {
border-left: 4px solid #000;
margin-left: -4px; /* the negative width of the border */
}
JS Bin Demo.
Edited to address my misunderstanding/mis-reading of the question.
This approach is kind of a hack, but is achievable using the same mark-up as in the previous demo, but more complex CSS:
#left,
#right {
width: 40%;
float: left;
padding: 1em;
}
#left {
border-right: 4px solid #000;
}
#right {
border-left: 4px solid #000;
margin-left: -4px; /* the negative width of the border */
}
#right p,
#left p {
border-left: 1px solid #ccc;
border-right: 1px solid #ccc;
margin: 0;
padding: 0 0.5em 1em 0.5em;
}
#right p:first-child,
#left p:first-child {
padding-top 1em;
border-top: 1px solid #ccc;
}
#right p:last-child,
#left p:last-child {
border-bottom: 1px solid #ccc;
}
Demo at JS Bin.
This won't be cross-browser friendly, though, IE (for certain) is likely to have problems with, at least, the :last-child pseudo-selector, so a JavaScript solution might be better for you in this instance. Although there is a more simple option to wrap the inner divs (in this case the #left and #right divs) in another div:
<div id="wrap">
<div id="left">
<div class="innerWrap">
<!-- content -->
</div>
</div>
<div id="right">
<div class="innerWrap">
<!-- content -->
</div>
</div>
</div>
Which can be used with the css:
#left,
#right {
width: 40%;
float: left;
padding: 1em;
}
#left {
border-right: 4px solid #000;
}
#right {
border-left: 4px solid #000;
margin-left: -4px; /* the negative width of the border */
}
div.innerWrap {
border: 1px solid #000;
}
Demo at JS Bin
But, while that's more cross-browser friendly, it does start a descent into the madness that is divitis.
try div {overflow:auto } where DIV is the container. or you can use the clearing DIV which you have to add after DIV 2 and before the main DIV .clear { clear:both }
EDIT: I overlooked - you wanted the DIVs to be set at equal height? That's not gonna happen due to the fact that it's a free flow document. You will need to use Javascript where it can look at the tallest DIV and set other DIV to match that height.
http://www.kensfi.com/set-a-div-height-equal-with-of-another-div/
considering you want this to degrade nicely all the way back to IE 6 have you considered a 3-column table with the center column with width of 1px band background-color of your divider color? outside olumns being the containers of your DIVs
I'm partial to JS in this case. If you assign an id to each div, then at the end of the loading of content call something like this (this is NOT REAL CODE):
if (get(div1).offsetHeight > get(div2).offsetHeight( {
div1.borderRight = 1px;
else
div2.borderLeft = 1px;
Oh...I may have misread that. If you want the divider to stretch the entire parent div, then set div1.style.height to divParent.clientHeight and add the border to it.