JSFiddle demo
In a dropdown with a container element set to display: inline-block, there is a label (always visible, toggles dropdown overlay), and the overlay element itself. I am setting the overlay container to height: 0 and wishing to allow the overlay contents to exceed the height of the container, without affecting any parent elements. However, I am seeing some strange results - the overlay container is causing the parent of the dropdown to fully enclose the overlay contents too!
In the following HTML, ib = the inline block and h0 = the height:0 overlay container. See the jsfiddle demo to see it in action.
<div>
Sort by this
<span id="ib">
<span>LABEL</span>
<div id="h0">
DROPDOWN<br />
</div>
</span>
</div>
I don't wish to use position: absolute on the overlay, as I would like the contents of the overlay to drive the final width of the label. Surprisingly, I can achieve the desired outcome with the following css:
#ib { display: inline-flex; flex-direction: column; }
I'm happy to use that workaround for now, but also interested in the "why" behind this bizarre effect.
Your issue is about vertical-align rule for inline block elements. By default it baseline, here is some spec:
Baseline: Align the baseline of the box with the baseline of the parent box.
See also:
The height of each inline-level box in the line box is calculated. For replaced elements, inline-block elements, and inline-table elements, this is the height of their margin box; for inline boxes, this is their 'line-height'.
source: https://www.w3.org/TR/CSS2/visudet.html#line-height
and
CSS assumes that every font has font metrics that specify a characteristic height above the baseline and a depth below it. In this section we use A to mean that height (for a given font at a given size) and D the depth. We also define AD = A + D, the distance from the top to the bottom.
source: https://www.w3.org/TR/CSS2/visudet.html#inline-box-height
So your fix is ignore default inline-level block height setted via line-height and font-size by setting vertical-align: top/bottom/middle/text-top/text-bottom by your choice.
And fixed code:
.dropdown {
display: inline-block;
vertical-align: top;
}
.overlay {
height: 0;
}
<div>
sort by this
<span class="dropdown">
<span>LABEL</span>
<div class="overlay">
DROPDOWN<br />
DROPDOWN<br />
DROPDOWN
</div>
</span>
</div>
<hr />
Problem:
I am trying to create a dropdown. When input is focused, the div below it appears. But I expect the div to be at higher z-index. If the div is at higher z-index, then button below it will be covered, but right now div does not take any z-index.
Code:
I have this example on which I am working now:
class Main extends React.Component {
render() {
return( <div style={{width: 200}}>
<input className="input" />
<div className="dropdown" style={{ position: 'absolute', zIndex: 9999 }}></div>
<button>Hello</button>
</div>
)
}
}
ReactDOM.render(
<Main></Main>,
document.getElementById('example')
);
Here is CSS:
html, body {
height: 100%
}
body {
background: #333;
font-family: Helvetica Neue;
}
input {
width: 100%;
}
input + div.dropdown {
background-color: #ffff00;
display: none;
height: 200px;
width: 100%;
}
input:focus + div.dropdown {
display: flex;
}
Here is the plunker of the same code:
https://plnkr.co/edit/B8Kjhv6rUOv0s7QKotxI?p=preview
What I tried:
If I change the position of dropdown div to absolute, then z-index is applied, but the width of the div is equal to the screen width.
I am creating a reusable component, so I can't give fixed width.
Note:
Fixed width given to main div is just an example but in actual use case, the width of main div will be automatically determined by the 100% width of its parent component.
In Summary:
Declare position relative on the containing (parent) element to allow a measure of control over absolutely positioned sibling element's width.
About positioned elements and stacking context:
A few things to consider when dealing with positioned elements and stacking context:
z-index property values will only apply to positioned elements.
positioned elements are elements with position property values defined as absolute, fixed, relative or
stickyexperimental; this does not include static,
the default positioning of any element.
When declaring an element as an absolutely positioned element
(absolute or fixed) you are removing the element from the
natural document flow; which simply means the element is no longer
interacting with sibling elements in the way relative or static
elements do (imagine the element "siting above" the rest of the DOM).
By default, an absolutely positioned element's position is
"relative" to the window; this means if you offset its position
with left or right property values it'll move a distance equal to
the property value from the window. You can position an element
with a position property value of absolute (not fixed) relative
to any containing element if you declare relative positioning to
that containing element.
This was the issue you were observing in your use-case; since the
nested .dropdown element was positioned absolute it was taken out
of the natural flow and occupied the full available width of the
containing document, so in order to restrict it to the width of its
containing element, position: relative should be declared on its
containing element, e.g:
<div style="width:200px;position: relative;" data-reactid=".0">
<input class="input" data-reactid=".0.0">
<div class="dropdown" style="position:absolute;z-index:9999;" data-reactid=".0.1"></div>
<button data-reactid=".0.2">Hello</button>
</div>
It would probably be better (and more scallable) to attribute a class selector to this element as well so that you can manage and maintain these styles externally with other declared styles (in style.css), e.g:
Amended html structure:
<div class="foobar" data-reactid=".0">
<input class="input" data-reactid=".0.0">
<div class="dropdown" style="position:absolute;z-index:9999;" data-reactid=".0.1"></div>
<button data-reactid=".0.2">Hello</button>
</div>
Additional css declarations:
.foobar {
width:200px;
position: relative; /* required for nested absolute element */
}
Reference:
position - CSS | MDN
z-index - CSS | MDN
I have this HTML code:
<div class="header">
<div class="desc">Description</div>
<div class="logo"><img src=""/></div>
<div class="navbar"></div></div>
.header has a height of 150px. .navbar has a height of 20px. When the user scrolls, I want .navbar to stick at the top. So I went to the CSS and set position:sticky and top:0. But this didn't work. I initially thought that firefox is not supporting position:sticky, but that's not the case because I was able to see a working demo of it. I googled about it but found nothing helpful. Anyone knows why this is not working?
Position sticky was not working for me due to the body element having overflow-x: hidden; set.
The 2 most common culprits why position: sticky; might not work are:
You haven't defined top: 0;, bottom: 0;, left: 0 or something similar
One of the parents of your sticky element has overflow (x or y) set to hidden, scroll or auto.
For me it was the first one.
It works fine if you move the navbar outside the header. See below. For the reason, according to MDN:
The element is positioned according to the normal flow of the document, and then offset relative to its flow root and containing block based on the values of top, right, bottom, and left.
For the containing block:
The containing block is the ancestor to which the element is relatively positioned
So, when I do not misunderstand, the navbar is positioned at offset 0 within the header as soon as it is scrolled outside the viewport (which, clearly, means, you can't see it anymore).
.navbar {
background: hotpink;
width: 100%;
height: 50px;
position: sticky;
top: 0;
}
.header {
height: 150px;
background: grey;
}
body {
height: 800px;
position: relative;
}
<div class="header">
<div class="desc">Description</div>
<div class="logo"><img src="" /></div>
</div>
<div class="navbar"></div>
To expand from the answers above and some information to make it work with flexbox parent and overflow other than visible (the examples below assume you use vertical - sticky with either top or bottom set to a certain value and position set to sticky):
The most frequent case is you have an ancestor element (not just immediate parent) with overflow property set to something other than visible and as a result there is no space is left to stick around.
To quickly find out if this is the case, you can run this script in the browser console (please make sure you change the .your-sticky-element class to your element's selector):
var stickyElement = document.querySelector('.your-sticky-element');
var parent = stickyElement.parentElement;
while (parent) {
var hasOverflow = getComputedStyle(parent).overflow;
if(hasOverflow != 'visible') {
console.log(hasOverflow, parent);
}
parent = parent.parentElement;
}
SOLUTION:
a) If you found there is overflow set, and you can remove it, this should solve it
b) If you have to keep your overflow setting, you have to make the parent element's height higher than the sticky element's height. If the parent element has no height or the sticky element fills up all the height, it means there is simply no place to stick within when the page is scrolled. It doesn't need to an explicit height (vertical), but you can inspect to see if your sticky element has extra space left after itself.
Parent is not higher than the sticky element to leave extra space. This particular case can be caused by different circumstances but the solution to this is the same above, please see 1.b
If your sticky element's parent is a flexbox (align-items has default value of normal) or grid, and if the sticky element itself doesn't have a proper align-self set, there will be no space left for the sticky element to hold when scrolling (for example, if it is align-self: stretch or auto [default value]). This is because the child element is stretched to fill up the height of the parent.
SOLUTION:
In this case, align-self: flex-start set for the sticky element can fix the problem because in the element will stand at the start, leaving extra space after itself.
Guide: There are much more complex circumstances both in the case of flexboxes and without it, but the general rule of thumb is your sticky element needs space within the parent to be sticky when scrolled.
Somehow your code only works when the .navbar element is not inside another container like the header. I moved it out and then it works fine. I created a codepen snippet for that, check it out
<header>
<div class="logo">Logo</div>
<div class="description"><div>Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo, veritatis.</div></div>
</header>
<div class="navbar">
<ul>
<li>navitem1</li>
<li>navitem2</li>
<li>navitem3</li>
<li>navitem4</li>
</ul>
</div>
Right now position:sticky is supported quite good as you can see on canIuse. Of course IE currently has no support but the new Edge version will bring full support for this! I found some interesting articles about this topic:
Working demo (chrome,firefox 👍) https://codepen.io/simevidas/pen/JbdJRZ
Caniuse refernce: http://caniuse.com/#search=sticky
sticky article on MDN including latest browser support table https://developer.mozilla.org/en-US/docs/Web/CSS/position#Sticky_positioning
But there are good news on the horizon. I think better browser support will follow the next time.
Adding more content after nav inside header provides sticky behavior, but only for a moment - if the user scrolls down too much, nav will disappear with header, since it can't jump out below header's bottom border.
Thus, the only solution with pure CSS is to put nav inside element that is partially visible even after the user scrolls to the bottom of the page (directly inside body or inside some sort of container that spans to the bottom of the page or at least to the footer).
If this solution is not possible, the other way is to use JavaScript. Before transitioning to CSS, I used the following code (found similar jQuery solution somewhere long time ago, don't remember where, so the credit goes to the anonymous author; Vanilla JS can be easily obtained from this):
$(document).ready(function () {
var sticky_navigation_offset_top = $('nav').offset().top;
var sticky_navigation = function () {
var scroll_top = $(window).scrollTop();
if (scroll_top > sticky_navigation_offset_top) {
$('nav').css({
'position': 'fixed',
'top': 0,
'left': 0,
'right': 0,
'margin-left': 'auto',
'margin-right': 'auto'
});
} else {
$('nav').css({
'position': 'relative'
});
}
};
sticky_navigation();
$(window).scroll(function () {
sticky_navigation();
});
});
Looks like if you try to set sticky a container which has many children nodes inside, instead of them being wrapped in a div, and the parent of sticky container is flex, then it will not sticky. Just wrap the childs in a div fixed it for me.
Your HTML code as it is and write CSS class for navigation bar:
.header {
height: 150px;
background-color: #d1d1d1;
}
.navbar {
background: #999;
border-bottom: 1px solid #333;
border-top: 1px solid #333;
color: #FFF;
margin: 0;
padding: 2px 0 0 12px;
position: sticky;
top: -1px;
}
<div class="header">
<div class="desc">Description</div>
<div class="logo"><img src="" /></div>
<div class="navbar"></div>
</div>
Hope this will help
Met some not evident behaviour of horizontal sticky: if width is 100%, then sticky does not work. Width should be less, then container size.
My sticky header would only partly work ... after a couple of scrolls it would disappear but would work initially
It appears the problem was that I had the parent set to height 100%.
I didn't actually need this as the body one was enough so I removed and it and all was good.. sticks forever
Although this now breaks my footer from staying on the bottom when their is no content!
No huge compromises of the HTML structure need to be made to fix this issue. Simply add display: inline; to all of the sticky element's parents up until you get to the element you wish the sticky element to stick to.
Just to add something to #user56reinstatemonica8 great point...
If immediate parent of sticky node has display: flex sticky positioning could not work.
My guess is that culprit is align-items: stretch as default.
In a flex-direction: row scenario, align-items: stretch let children's height grow so that they are equal height.
So, to overcome this and make sticky work as expected with display: flex you can:
define align-items as center | start | baseline to immediate parent that has display: flex.
define align-self as center | start | baseline to sticky node.
define an explicit height to sticky node.
Lets say I have this
<div class="sectionContainer">
<div class="itemsContainer">
<div class="items"></div>
<div class="items"></div>
<div class="items"></div>
</div>
</div>
The css:
.itemsContainer
{
/* width:3000px works, however this is what I want to avoid saying explicitly.*/
}
.sectionContainer
{
width: 1000px;
overflow: auto;
}
.items
{
width: 1000px;
float: left;
}
The sectionContainer has some set width.
The items has some set width.
The items container does not have a set width; it will scale to the size of its contents.
The items container's overflow is set to hidden, so that one can scroll through the items within the div. The items within the div are horizontally displayed IE they are side-by-side, I'm currently doing this with a float.
How can I do this with CSS only? Is it possible? I'm not looking for a JavaScript solution right away but can resort to that if needed.
to be more specific, this would work if I specified the itemsContainer to have a width of 3000. But I'm guessing that since it is the child of its parent div, its width gets sized to 1000. I do not want to explicitly set the size of the itemsContainer because this should be based upon the number of items. If I add more items, I want the itemsContainer to change its width to contain all of those items without having to alter the CSS.
Thanks!
It's not possible for CSS to guess what you want to happen -- meaning that it wants to cascade elements downward as opposed to horizontally, which is what you want.
http://jsfiddle.net/9NHFa/
Set the itemsContainer width to a 100% X the number of elements.
.itemsContainer
{
width:300%; /* since you have 3 elements
}
To explain my problem, I'm trying to make a div wide enough to accommodate a dynamically generated title without wrapping it, but the div also has other content, which I want to wrap.
In other words:
CSS:
.box {
min-width:170px;
}
.box span.title {
font-size:24px;
white-space: nowrap;
}
.box span.text{
font-size:10px;
white-space: normal;
}
HTML:
<div class="box">
<span class="title">Title on one line</span><br />
<span class="text">This is the main body of text which I want to wrap as
required and have no effect on the width of the div.</span>
</div>
However, this is causing the div to expand to be wide enough to contain the main body of text on one line, which I want to wrap. I've tried various arrangements for CSS and the putting them all inside container divs and the like but I can't seem to get the box to be exactly wide enough to contain only the title without wrapping (but not less than the min width)
Is there any way to do this just in CSS? Note I don't want to set a max width as this just causes it to become a static size again, as the main body of text is always going to be enough to hit the max width. I also can't line break the body manually as it's dynamically generated.
Is this (jsFiddle) what you're trying to accomplish?
I just added display: table; to .box's CSS. This expands the main div to the width of the title span but wraps the text span.
Note: You can also set a constant width to prevent the div from expanding to the width of the window. This way it will still expand to the width of the title if it is larger than your constant width, but will not grow if the user drags out the window. In my example I added width: 100px; to demonstrate.
A working jQuery example:
http://jsfiddle.net/8AFcv/
$(function() {
$(".box").width($(".title").width());
})
For headlines you should use the <hN> tags (<h1>, <h2> etc).
For no text wrap:
white-space: nowrap;
On the element who's text you don't want to wrap.
Working Example on jsFiddle
If i understand your correctly you can easily set the same width for yours text as for yours title using JS or jQuery, for ex:
$('.text').width($('.title').width())
and run it at jQuery(document).ready or by event if you add it dynamically
Block elements such as divs extend as far as content pushes them, unless specified by explicit widths or heights.
A pure CSS solution for this is unlikely without setting a max-width on the div.
A pointer on CSS:
Don't include the tags in your selectors (i.e. tag.class) as you are then forced to use that tag with that class. Simply using .class will make it easier to change your markup (should you need to) as well as make your class extend its use to more than a single tag.