Kevin De Baerdemaeker

CSS for Javascript Developers

Modified 2024-09-27

Caveats with auto margins

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.

  1. Only block-direction margins collapse (vertical margin)
  2. Only in flow layout
  3. Only adjacent margins collapse; an element in between stops them from collapsing.
  4. The bigger number wins
  5. 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.
    1. blocked by padding or border
    2. blocked by a gap

By following these rules it also means that:

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:

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):

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.

  1. Add the 100% width on all the containers from your main container until html
  2. Put min-height on the wrapping container
  3. 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   @computer

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

  • Source
  • Recommended By

    Reason

    I suck at CSS


    Backlinks