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