Graduate Program KB

Module 4 - Flexbox

  • Flexbox is a layout mode used for controlling the distribution of elements along a primary axis, either horizontally or vertically
  • It offers more flexibility for working with a single dimension, in comparison to grid layout for two-dimensional layouts
  • Applying display: flex will toggle the flexbox algorithm for the element's children
    • The parent element still uses flow layout

Direction and Alignment

  • Consider the CSS below:

    • This will display elements in the inline direction and they'll be stretched vertically
    .wrapper {
        display: flex;
        flex-direction: row; 
        justify-content: flex-start; 
        align-items: stretch;
    }
    
    • flex-direction is a property which determines if elements are displayed along the horizontal axis or vertical axis
      • Values: row, column
    • justify-content is a property to push items away from each other. They can all be grouped at one end or be spaced apart in a certain way
      • Values: flex-start, flex-end, center, space-between, space-around, space-evenly
    • align-items is a property to align items in the opposite axis that the elements are displayed in (opposite of flex-direction)
      • Values: stretch, flex-start, flex-end, center, baseline
  • Centering an element is not as intuitive as setting center for justify-content and align-items

    • Initially, we aren't changing the layout mode of the parent, only the children
    • The simplest solution is to add min-height: 100vh, but it might have layout issues on mobile
    .wrapper {
        display: flex;
        justify-content: center; 
        align-items: center;
        min-height: 100vh;
    }
    
    • The other solution is to set height: 100% on the parent containers of the wrapper then set min-height: 100% instead
    • Now the wrapper height is based off the parent container's height
    html, body {
        height: 100%
    }
    
    .wrapper {
        display: flex;
        justify-content: center; 
        align-items: center;
        min-height: 100%;
    }
    

Alignment Tricks

  • Baseline alignment is the pattern of aligning the bottom of each element with one another

    • A common application of this is displaying a company's logo before the headers in a navigation bar
    • Just simply set align-items: baseline to align the bottom edge with one another
    • To also centre the logo and the headers in the navigation bar, we need a nested flexbox layout
    nav {
        height: 100px;
        display: flex;
        flex-direction: column;
        justify-content: center; 
    }
    
    ul {
        display: flex;
        justify-content: space-evenly;
        align-items: baseline;
    }
    
  • Instead of aligning groups of elements, we can also assign specific elements to have specific alignments

    • The align-self property manages this and has the same possible values as align-items

Growing and Shrinking

  • Depending on the constraints, width and height are more like suggestions. For example, it can't respect the property set if the window is shrunk too small

  • Main takeaways:

    • The two important sizes when dealing with flexbox are the minimum content size and the hypothetical size
    • The minimum content size is the smallest an item can get without its contents overflowing
    • Setting width in a flex row or height in a flex column, sets the hypothetical size. Though it's not guaranteed, more like a suggestion as previously mentioned
    • flex-basis has the same effect as width in a flex row or height in a column. Can use either interchangeably, but flex-basis will win if there's a conflict
    • flex-grow will allow a child element to consume any excess space in the container. If there's no excess space, there's no effect
    • flex-shrink will pick which item to consume space from if the container is too small. If there's no excess space, there's no effect
    • flex-shrink also can't shrink an element below its minimum content size, the same applies with flex-basis. However, width can scale an element below the minimum content size
  • flex-grow and flex-shrink use unitless values such as 1 or 10

    • This represents a ratio of the available space

    • flex-grow will consume a portion of the available space

      • If there are 3 elements of equal width taking up 100% of the space
      • Setting flex-grow: 3 to the first element will give it 60% of the space, shrinking the rest to 20%
    • flex-shrink will shrink a flex item by a factor of the value selected

      • If one container has a shrink factor of 6 and another container has a shrink factor of 2, then the first container will shrink 3x as fast as the rest when the space decreases
    • In the following example, main wants to consume 3 times as much space as nav or aside, getting 3 units of space

    <style>
        .row {
            display: flex;
        }
    
        nav, aside {
            flex-grow: 1;
        }
    
        main {
            flex-grow: 3;
        }
    </style>
    
    <div class"row">
        <nav></nav>
        <main></main>
        <aside></aside>
    </div>
    

Flex Shorthand

  • The flex property takes up to 3 values

    • flex-grow, a unitless value
    • flex-shrink, a unitless value
    • flex-basis, a length unit
  • flex: 1 will assign flex-grow: 1 but also set flex-basis: 0. It doesn't affect the default value for flex-shrink which is 1

  • flex-basis is a synonym for width in a flex row

    • Each child element is essentially shrunk to have a hypothetical width of 0px and then all the space is distributed between each child

Constraints

  • Properties like flex-grow, flex-shrink and flex-basis control the proportions between siblings in a flex container
  • If we want to set a hard limit, we can set the properties min-width, max-width, min-height and max-height
  • To reiterate, flex-basis works just like width, but respects constraints set by min-width and max-width

Shorthand Gotchas

  • When using flex shorthand, flex-basis is set to 0. This value will override any width set, basicaly width has no effect in the following snippet

    .item {
        flex: 1;
        width: 200px;
    }
    
    • To avoid this issue, flex-basis should be used instead
    • If flex-grow or flex-shrink also need to be set, then the shorthand can be used
    .item {
        flex: 1 1 200px;
    }
    

Wrapping

  • Flexbox has a similar concept to line-wrapping in flow layout, it has the flex-wrap property
    • If elements are overflowing then it will wrap to another row/column
    • To determine when wrapping occurs, you could set flex-basis to determine the minimum size of the element
    • Sometimes, elements that have been wrapped may be larger because it takes up all the available space on the new row/column
      • A way to make this more manageable is to set max-width
    <style>
        main {
            display: flex;
            flex-wrap: wrap;
        }
    
        article {
            flex: 1 1 150px;
            max-width: 250px;
        }
    
        article img {
            width: 100%;
        }
    </style>
    

Groups and Gaps

  • Consider the following snippet

    • Here, the two buttons are grouped with the logo. We want to separate them to the right side
    • There's no need to wrap the buttons in a <div> and treat the logos and buttons as two children, then apply justify-content
    • Instead, set margin-right: auto on the logo. This works because the element consumes all the extra space in the flex container and puts it in its right margin
    • Now, we want a bit of space between the buttons. The gap property can be set on the wrapper which adds a small space between child elements, not on the ends
    <Wrapper>
      <Logo>My Thing</Logo>
      <AuthButton>Log in</AuthButton>
      <AuthButton>Sign up</AuthButton>
    </Wrapper>
    
    const Wrapper = styled.header`
        display: flex;
        gap: 8px;
    `;
    
    const Logo = styled.a`
        font-size: 1.5rem;
        margin-right: auto;
    `;
    
    • Another solution is to have a Spacer component which takes in a dynamic width, it can be applicable to many parts of the UI
    <AuthButton>Log in</AuthButton>
    <Spacer size={8} />
    <AuthButton>Sign up</AuthButton>
    
    const Spacer = styled.div`
        min-width: ${p => p.size}px;
    `;
    

Ordering

  • Flexbox arranges items based on DOM order, the same with flex layout

  • But in flexbox, we can override this natural ordering with the flex-direction property

  • Commonly used values are row and column to set a container's primary axis but there are two more values

    • row-reverse and column-reverse just flips the order of elements so they stack from last to first
    • It works by swapping flex-start and flex-end. Setting flex-direction: row-reverse flips this on its head
    • If we want to flip the order of elements without changing their alignment, use justify-content
    .row {
        flex-direction: row-reverse;
        justify-content: flex-end;
    }
    
  • The order property can be used to control order, works similar to z-index

    • A child element with order: 2 will show up after a child with order: 1, but before a child with order: 5
  • Another mechanism for controlling order is setting flex-wrap: wrap-reverse which causes elements to wrap upwards rather than downwards

Flexbox Interactions

  • If a container uses a flexbox layout and its child element is also given a positioned layout, then positioned layout always wins the conflict

    • The .row flex container ignores the child <div>
    • There are some exceptions with relative and sticky positioning where an element is rendered in two layout modes but are still compatible
      • The element is first laid out inside the flex container, then transposed using top/left/right/bottom by positioned layout
    <style>
        .row {
            display: flex;
        }
    
        .btn {
            flex: 1;
            position: fixed;
            right: 0;
            bottom: 0;
        }
    </style>
    
    <section class="row">
        <div class="btn"></div>
    </section>
    
  • Margin collapse is exclusive to flow layout, it won't apply to elements laid out inside a flexbox parent

  • The flexbox algorithm also supports z-index, child elements don't have to set position: relative, unlike elements in a flow layout

    • Grid layout can also use z-index without position: relative