CSS for Javascript Developers
Modified 2024-09-27
Caveats with auto margins
- only works for horizontal margins
- only works on elements with an explicit width
Using negative margin to oddfset padding from parent
Applying negative margin lets certain child elements break free from parent's padding, which can lead to simple, but cool effects.
<iframe height="480px" width="50%" frameborder="0" src="snippets/html/negative-margin/index.html" />Which elements affect the block layout?
A replaced element is an element which embeds something inside. Such as <img
/>, <video /> and <canvas />. The width and height of these replaced
elements do get affected, so they can affect the block layout.
The button element is another inline element which can be given a width and a height.
How to get rid of the magic space around images?
The vertical space of an inline element such as <img />, is affected just
how text is affected by line-height. So you can either remove the gap
line-height: 0 or transform it to a block element display: block.
The horizontal space between images is due to HTML being space-senstive.
Max Width Wrapper
The max width wrapper fills the available space on smaller viewports, sets a maximum width, centers itself and includes a bit of horizontal padding to prevent the wrapper from being smushed against the edges.
.max-width-wrapper {
max-width: 500px;
margin-left: auto;
margin-tight: auto;
padding-left: 16px;
padding-right: 16px;
}Height algorithm calculation
The parent element will wrap itself tight around it's children. A percentage based height is depends on the height of the parent. However, the parent needs to look at it's children to determine the height. We have a chicken-egg problem.
The solution is to apply height: 100% before the main one (including body and
html), put a min-height: 100% on the wrapper and never use percentage based
heights within that wrapper.
Rules of Margin Collapse
In stead of remembering the rules by hard it's better to develop an intuition when to start paying attention the margin collapses if applying a margin doesn't have the desired effect.
- Only block-direction margins collapse (vertical margin)
- Only in flow layout
- Only adjacent margins collapse; an element in between stops them from collapsing.
- The bigger number wins
- Nesting an element does not prevent collapsing; margins always increase the
distance between the siblings, even if it means the margin is transferred to
the parent element.The margins must be touching in order for them to
collapse.
- blocked by padding or border
- blocked by a gap
By following these rules it also means that:
- more than two margins can collapse
- can collapse in the same direction
- a negative and positive margin are added up together
Using margins effectively
Margin is like putting glue on something before you’ve decided what to stick it to, or if it should be stuck to anything. ---Heydon Pickering
Avoid using margin on reusable components, because applying a margin is assuming the context where the component is going to be used (which is not always the case).
Margins for layout components is good usage.
Pseudo-elements purely for decoration
The CSS pseudo-elements act like secret spans and should be used purely for decorative purposes, due to inconsistent accessibility behaviors of devices.
Conditionally Rendering Elements
If the hidden elements are not memory intensive, prefer display: none over
conditionally rendering, as the browser is better at hiding elements. The
animations also become smoother.
Prefer logical properties and values
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties
The block dimension flows perpendicular compared to the text of a line, while the inline dimension runs parallel to the flow of the text. In English the block dimension is vertical, while the inline dimension is horizontal.
TODO CSS for JS
Sticky Positioning
The thought behind the property is that we you scroll, the element can "stick" to the edge of the parent, while staying within their parent's box. Sticky elements will never scroll outside its parent container. This bounding effect can be a cool effect when scrolling through a list of sections in a portfolio.
To enable the position:sticky, you'll need to apply a top, right, bottom or
left property to indicate which side of the container it has to stick to. These
properties are seen as the buffer before the sticky behaviour gets activated.
Just like relative or static elements, sticky elements are laid out in the flow and take up real space in the layout.
A common problem for a sticky element to not work as intended is when one of the ancestor elements has set an overflow property. It's possible to find these overflow elements via:
const selector = '.the-fixed-child';
function findCulprits(elem) {
if (!elem) {
throw new Error(
'Could not find element with that selector'
);
}
let parent = elem.parentElement;
while (parent) {
const hasOverflow = getComputedStyle(parent).overflow;
if (hasOverflow !== 'visible') {
console.log(hasOverflow, parent);
}
parent = parent.parentElement;
}
}
findCulprits(document.querySelector(selector));Cute little trick to make the overflowing elements have a bit nicer UX/UI is to break out the overflowing element by removing the padding of the parent, by applying negative margin on the child. At that point the overflowing scroll bar is now aligned with the parent element, and makes the UI look clean.
See: https://courses.joshwcomeau.com/css-for-js/02-rendering-logic-2/15-sticky for UI examples with overflow sticky.
Hidden Content
Fixed Positioning
The fixed elements can only be contained by the viewport and it's initial offset is indicate by it's containing block. For example, if a fixed child has parent which is 400px from the top, the fixed element's initial "offset" will be the regular flow layout position it would be and is "burned" in that position on the viewport. Scrolling has therefore no effect on these elements.
Transformed parents and grandparents can't have fixed children, as these parents become the containing block of the fixed element, which essentially acts the same as an absolute element.
To find transformed ancestors:
const selector = '.the-fixed-child';
function findCulprits(elem) {
if (!elem) {
throw new Error(
'Could not find element with that selector'
);
}
let parent = elem.parentElement;
while (parent) {
const {
transform,
willChange
} = getComputedStyle(parent);
if (transform !== 'none' || willChange === 'transform') {
console.warn(
'🚨 Found a culprit! 🚨\n',
parent,
{ transform, willChange }
);
}
parent = parent.parentElement;
}
}
findCulprits(document.querySelector(selector));Fixed Positioning
Stacking Contexts
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context If you want to layer, prefer positioned layout as flow layout doesn't work well with that (rendering in Browser is wonky).
The most common confusion about stacking contexts is when an accidental stacking context is created on an element which shouldn't define a new stacking context. The most common one is by setting a position other than static and a z-index.
Other ways which create a new stacking context:
- Any opacity less than 1
- A fixed or sticky position
- Applying a mix-blend-mode other than normal
- Adding a z-index to a child inside a display: flex or display: grid container
- Using transform, filter, clip-path or perspective
Explicitly declaring a stacking context with
isolation: isolateThis should preferably be used on the parent as soon as a child tries to add z-indexes.Possibly add this to the root of you react application to avoid weird effects with portals.
A must-have extension to have is the CSS Stacking Context Inspector. Firefox: https://addons.mozilla.org/en-US/firefox/addon/css-stacking-context-inspector/ Chrome: https://chrome.google.com/webstore/detail/css-stacking-context-insp/apjeljpachdcjkgnamgppgfkmddadcki
Think of fixing stacking issues with z-index the same as using !important on properties. It's anti-pattern which should be used carefully.
Needs more reading tbh!
The browser renders the the static elements, then it will do a second pass and render the non-static elements. Elements which create a new stacking context are also drawn during this pass.
Stacking Contexts
Absolute Positioning
The elements get taken out of flow layout, which essentially means that the element doesn't exist when the browser is rendering the layout on the page flow. Think about those elements being ghosts, floating around on the page, but they don't have space to fill in the regular layout.
If you only define a right/left when absolutely positioning, the element gets absolutely positioned horizontally, however vertically speaking it kinda respects the the flow layout, without being inside the flow layout.
Incidentally, not applying any top/right/bottom/left on an absolutely positioned element will just take the element out of the flow layout, but the element will still spawn near it's original position inside the layout. The behaviour is super important for creating elements which float around their parent elements (such as tooltip).
Absollute positioning is essentional for whimsical effects and tooltips.
.tooltip-trigger {
display: inline-block;
font-weight: bold;
color: seagreen;
}
.tooltip {
/* Hide the tooltip by default */
display: none;
background: white;
border: 1px solid;
color: black;
position: absolute;
max-width: 150px;
}
/* Show the tooltip when hovering over the wrapper */
.tooltip-trigger:hover .tooltip {
display: block;
}Centering
If all the following conditions are met, the margin:auto makes sure that the element is vertically AND horizontally perfectly centered.
position: absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
width: 50px;
height: 50px;
margin: auto;Parent/Child relationship !!IMPORTANT!!
Absolutely-positioned children can only be constraint by their parents' bounding box if somewhere in the parental tree an element has a non-static positioning.
Absolute Positioning
Relative Positioning
Any value, besides, static for the position property gives access to new
properties like top, right, bottom, left, which are used to position the
element relative to its natural position.
The position doesn't have a visual impact the layout, meaning applying a margin-top will shift the whole layout downwards, while a top makes sure the element breaks out of it's own container, without impacting the rest of the layout. It's purely cosmetic effect, versus the layout shifts when applying margins.
It works on both inline and block elements, which means that relative positioning could be used as the only solution to nudge an inline element in a certain direction.
Absolute Positioning
Positioned Layout != Flow Layout
position is set to relative, absolute, fixed or sticky. Flow layout is using the default "static" position.
Positioned Layout != Flow Layout
Margin Collapse
Think of margins as personal space between elements, so the margins will overlap each other by giving each element enough personal space.
Margin collapse is super tricky and has many rules (better to develop an intuition rather than remembering these by hard):
- Vertical margin collapse, horizontal don't (in horizontally stacked writing modes)
- Only happens in flow layout, not flexbox or grid
- Only adjecent elements collapse (placing a hr in between breaks the margin collapse)
- The bigger margin wins
Nesting doesn't prevent collapsing, because margins is meant to increase distance between sibilings, not increase the gap between a child and it's parent's bounding box => that's for padding.
Note that margins only collapse when they are touching. If padding, a border or height gap sits in between the margins won't collapse.
- Margin can collapse in the same direction => parent and child who both define margins might get their margins collapsed (bigger one wins again). Sometimes confusing when the parent has no margin (0px) and the child has 40px, it will collapse the margin of the parent by 40px and not pushing away the child from the parent by 40px.
- More than two margins can collapse
- The most negative number wins
- Multiple positive and negative numbers boils down to finding the largest of numbers and adding them together to see the end result margin
Using margin effectivly
Reusable components should be as unopinionated as possible, meaning they should be free of margin that goes beyond the bounds of the of the border of the box. These reusable components might need a different margin depending on different contexts which should be handled by layout components.
Throwing margin's willy nilly is asking for problems.
Margin is like putting glue on something before you’ve decided what to stick it to, or if it should be stuck to anything. ---Heydon Pickering
Module 2: Rendering Logic II
Create example for max-width-wrapper
Flow Layout
Flow Layout is the default layout mode. Other modes are: Flexible Box and Grid Layout. Any block, inline or inline-block element is Flow Layout.
Inline elements don't want to make a fuss
Inline elements can be shifted in the inline direction, but shifting these in the block direction can have undesirable results or is plainly useless (height and width are unaffected).
Replaced elements are inline elements which embed a foreign object. These elements can affect block layout by giving them explicit dimensions. Think of it as the dimensions being applied on the foreign object, while the wrapper stays inline.
Block elements don't share
The box expands until the whole horizontal space is filled. Unless width:
fit-content; is applied.
Inline elements have magic space
The browser treats inline elements as if they're typography. That means the
inline elements are affected by line-height. It's possible to remove the extra
space by adding display:block on the img or line-heigth: 0 of the parent.
The inline elements are also space sensitive. These issues are more generally fixed by applying a flexbox or floats.
Inline elements can line-wrap
The inline elements can create shapes other than boxes, which span across multiple lines. Applying horizontal padding is only applied at the tips.
Using box-direction-break: clone allows to treat each wrapped line as it's own
little box and copies the styles to teach one. It's useful for when you want to
apply padding to wrapped lines.
The deal with inline-block
An element which acts as a block from within, but from the outside it looks like an inline element. Read: Inline element which can be styled as a block element.
The problem with inline-block elements is that these can't wrap to the next line.
Width Algorithms
Block elements don't have a default width of 100%, but auto. Percentage
based widths are based on the parent element's content space. So if the body tag
makes 720px available, any child with width: 100% will also be 720px wide.
The width can be defined as a measurement (%, px, rem) or keywords (auto, fit-content).
The min-content bases on it's child elements to make the box the smallest as
possible. The max-content pays no attention to constraints of the parent
container. It produces a cool effect where the sides of the box is flush with
the letters. Meanwhile fit-content makes is "just right".
The "max width" wrapper.
.max-width-wrapper {
max-width: 500px;
margin-left: auto;
margin-tight: auto;
padding-left: 16px;
padding-right: 16px;
}Height Algorithms
The default behavior of block-level element is take up as little height as possible. Percentage based heights often have no effect because height tends to look down the tree to determine the size of it's contents. So when you child is asking for a 100% height on a dynamic height both will be looking at each other for guidance.
Solving the 100% height problem.
- Add the 100% width on all the containers from your main container until html
- Put min-height on the wrapping container
- Don't use percentage-based heights
html, body {
height: 100%;
}
.wrapper {
min-height: 100%
}The approach is better than using vh, because on mobile scrolling down changes
the viewport height (removing the search bar and fooder controls.)
A sticky footer is a common UI challenge which unfortunately can be nicely (absolute positioning is the Jacky option) solved with flexbox.
html, body {
height: 100%;
}
.wrapper {
display: flex;
flex-direction: column;
min-height: 100%;
}
footer {
margin-top: auto; /* The auto margin forced the footer all the way down */
}A permanent footer is nowadays best done with flexbox.
Margin Collapse
Create example for thinking outside the box (negative margin)
Margin
The only property which supports negative values (padding and border does not), effectivly placing an element outside its parent. Negative margin can be used to move a title a little bit out of it's container, stretching images by wrapping the image in a div, but keep the padding for the text.
Margin is about changing the gap between element. Not about changing the position of one element, because it also affects the sibilings.
Auto margin only works on horizontal margins and elements with an explicit width in "default flow layout". Block elements naturally grow to the available space, so we need a width in order to center it.
Flow Layout
TODO Re-read overflow section from cssforjs
https://courses.joshwcomeau.com/css-for-js/02-rendering-logic-2/14-overflow
If it's 100% certain the UI will need to scroll, it's recommended to
overflow-y: scroll in stead of overflow: auto, because the sudden appearance
of the scrollbar will cause the content to shift, which causes a repaint.
Always make sure to properly document why you'd be using overflow: hidden.
Horizontal overflow with images can be achieved by applying white-space:
nowrap on the elements. The property only works for inline/inline-block
elements.
Overflows
Metadata
- Creator(s)
Josh Comeau
I suck at CSS