Graduate Program KB

Module 1 - Rendering Logic I

Built-in Declarations and Inheritance

  • Each browser includes their own user-agent stylesheet for specifying the default styling of HTML tags

  • Certain CSS properties are inheritable, meaning a property specified on a parent element can be passed to the children, even if the property is not specified on the child

    • Similar to JavaScript's prototypal chain, where it searches the current element for a property, otherwise it moves up the chain, checking its parents for the property
    • color is inheritable, when applied to an element, that value gets passed down to the child and grandchild elements
    • border is not inheritable, it must be explicitly added to all elements instead
    • List of inheritable properties:
      • border-collapse, border-spacing, caption-side, color, cursor, direction, empty-cells, font-family, font-size, font-style, font-variant, font-weight, font-size-adjust, font-stretch, font, letter-spacing, line-height, list-style-image, list-style-position, list-style-type, list-style, orphans, quotes, tab-size, text-align, text-align-last, text-decoration-color, text-indent, text-justify, text-shadow, text-transform, visibility, white-space, widows, word-break, word-spacing, word-wrap
  • Inheritance can be forced by explicitly adding the styling in the anchor tags

    • color is overwritten by the default style color: -webkit-link despite being inheritable
    <p style="color: red;"> Text </p>
    

Cascade

  • If there are conflicting styles between selectors of a class and tag, the class has precedence. But, IDs are more specific than classes
  • To implement a cascade in JavaScript, use the spread syntax
    • The order to merge the styles is determined by specificity, where the styles merged later overwrite the conflicting styles and non-conflicting styles are kept
    const appliedStyles = {
        ...tagStyles,
        ...classStyles,
        ...idStyles
    }
    

Block and Inline Directions

  • Words are combined into blocks, such as headings and paragraphs. A page is constructed out of blocks, placed on top of one another

    • Block direction is vertical
    • Inline direction is horizontal
  • Logical properties provide alternative styles depending on internalisation. Consider languages that read in different directions

    • Here's the built-in styles for <p> tags in Chrome
    p {
        display: block;
        margin-block-start: 1em;    /* Top, magin-top */
        margin-block-end: 1em;      /* Bottom, margin--bottom */
        margin-inline-start: 0px;   /* Left, margin-left */
        margin-inline-end: 0px;     /* Right, margin-right */
    }
    

Box Model

  • The four aspects that make up the box model are content, padding, border and margin

  • The browser debugging tool can be used to select elements and view properties of its box model (or Ctrl + Shift + C)

    • Scrolling to the bottom, you can view the box sizing
    |----------------------------------------------------------|
    |  margin                      -                           |
    |      |--------------------------------------------|      |
    |      | border                -                    |      |
    |      |      |------------------------------|      |      |
    |      |      |  padding       -             |      |      |
    |      |      |      -------------------     |      |      |
    |      |      |      |    512  x  512  |     |      |      |
    |      |      |      -------------------     |      |      |
    |      |      |                              |      |      |
    |      |      |------------------------------|      |      |
    |      |                                            |      |
    |      |--------------------------------------------|      |
    |                                                          |
    |----------------------------------------------------------|
    
  • Consider the following CSS:

    <style>
        section {
            width: 500px;
        }
        .box {
            width: 100%;
            padding: 20px;
            border: 4px solid;
        }
    </style>
    
    • The size of the rectangle drawn is 548px wide, not 500px
    • Setting .box to have width: 100% means that the box's content size should be equal to the available space, 500px
    • Padding and border are added to each side, hence, 500px + (20px * 2) + (4px * 2) = 548px
  • box-sizing is a CSS property that allows us to change rules for size calculations

    • content-box takes inner connect content into account only but offers an alternative value, border-box
    • box-sizing: border-box allows sizing to behave more intuitively
    <style>
        section {
            width: 500px;
        }
        .box {
            width: 100%;
            padding: 20px;
            border: 4px solid;
            box-sizing: border-box;     /* Now the rectangle is 500px, the content-size is shrunk to accommodate the padding and border */
        }
    </style>
    
    • To set this to default for all elements, add the following CSS snippet
    <style>
        *,
        *::before,
        *::after {
            box-sizing: border-box;
        }
    </style>
    

Padding

  • Padding is the inner-space surrounding the content

    • If three values are provided to padding, it sets the top, right, bottom and mirrors the right value to the left
    .even-padding {
        padding: 20px;  /* all sides */
    }
    
    .two-way-padding {
        padding: 15px 30px;     /* top/bottom left/right */
    }
    
    .asymmetric-padding {
        padding: 10px 20px 30px 40px;   /* top right bottom left (picture a clock) */
    }
    
    /* Logical properties */
    .asymmetric-logical-padding {
        padding-block-start: 20px;      /* top */
        padding-block-end: 40px;        /* bottom */
        padding-inline-start: 60px;     /* left */
        padding-inline-end: 80px;       /* right */
    }
    
    • Long-form properties overwrite the relevant value in shorthand properties
    • The order matters, whichever declaration is specified last will overwrite the first declaration
    .box {
        padding: 48px;
        padding-bottom: 0;
    }
    
  • Most commonly used units for padding are px, em and rem

    • Percentage units are not-intuitive, they are based on the element's available width

Border

  • Border is the space surrounding the padding, unlike the other properties of the box model, it has a cosmmetic component

    • border can specify the width, style and colour. Style is the only required field
    .box {
        border: 3px solid blue;
    }
    
    .long-form-box {
        border-width: 3px;
        border-style: solid;
        border-color: blue;
    }
    
  • If border colour is not specified, it will use the element text's colour

    • To specify this behaviour explicity, use the currentColor keyword
    .box {
        color: green;
        border: 3px solid currentColor;
        box-shadow: 2px 2px 2px currentColor;
    }
    
  • The border-radius property rounds an element, even without a border

    • It accepts fields for specific corners, similar to padding
    .box {
        border-radius: 10px 20px 30px 40px;     /* top-left, top-right, bottom-right, bottom-left (picture a clock) */
    }
    
    .long-form-box {
        border-top-left-radius: 10px;
        border-top-right-radius: 20px;
        border-bottom-right-radius: 30px;
        border-bottom-left-radius: 40px;
    }
    
    .percentage-box {
        border-radius: 50%;     /* Form circles or ovals, each corner is a percentage of the total width/height */
    }
    
  • The difference between border and outline is outline doesn't affect layout, it's more like a box-shadow (effect draped over an element without affecting size)

    • Outlines share the same properties of borders, but with the outline prefix instead for the property names
    • Adding the outline and border properties simultaneously will stack the outline on top of the border, essentially forming a two-layer border
    • outline-offset will add a gap between the element and its outline

Margin

  • Margin increases space around an element, it's the outer layer in the box model

    .box {
        margin: 20px;   /* all sides */
    }
    
    .asymmetric-box {
        margin: 20px 10px;      /* top/bottom left/right */
    }
    
    .long-form-box {
        margin-top: 10px;
        margin-left: 20px;
        margin-right: 30px;
        margin-bottom: 40px;
    }
    
    /* Logical properties */
    .asymmetric-logical-box {
        margin-block-start: 20px;       /* top */
        margin-block-end: 40px;         /* bottom */
        margin-inline-start: 60px;      /* left */
        margin-inline-end: 80px;        /* right */
    }
    
  • Only with margin, negative values can be specified

    • Can be used to pull the current element outside its parent element
    • Setting a negative margin for a direction will pull the element in the opposite direction specified
    • Don't think of margin as a way to change positions, rather, it's used to change the gap between elements
    • Negative margins can affect the position of all siblings
  • Auto margins can be used to centre child elements in a container

    • The auto keyword will try to fill the maximum available space on a side
    • However, it doesn't apply to vertical margins (margin-top and margin-bottom). auto would be equivalent to 0px here
    • A width property must also be explicited provided, since block elements naturally grow to fill the available horizontal space
    • The following CSS snippet applies an equal amount of margin to the left and right side of an element
    .content {
        width: 50%;
        margin-left: auto;
        margin-right: auto;
    }
    
  • Special replaced elements such as <img>, <video> and <canvas> tags don't automatically expand to fill available space

    • They rely on an intrinsic size, so if you want to stretch them out, wrap them in another container
    • width: auto means to use natural width for these elements, not "stretch out to fill the space"
    .stretch {
        margin-left: -32px;
        margin-right: -32px;
    }
    
    img {
        display: block;
        width: 100%;
    }
    

Flow Layout

  • HTML elements will have its layout calculated by a layout algorithm, called layout modes

  • Flow layout is the default layout mode

    • Block elements: headings, paragraphs, footers, asides, the content that makes up the page
    • Inline elements: links, bold text, some form of text styling usually
    • block stacks elements in the block direction, inline stacks elements in the inline direction
  • Block elements will greedily expand to fill all horizontal space

    • Use fit-content keyword to shrink a block down to the minimum required size for text
    h2 {
        width: fit-content;
    }
    
  • Inline elements can have extra pixels in size that aren't specified

    • Consider an image with a height and width of 300px wrapped in a container <div>

    • The parent container could have a slightly larger height of 306px for example, because the browser treats inline elements like typography

    • This extra bit of space is usually to create gaps between lines in a paragraph so they aren't crammed tightly, but it's applied here for the image

    • To resolve this issue, there are two ways:

      • Set images to display: block
      • Set the line-height property on the container <div> to 0
    • Now consider multiple images stacked in the inline direction, there would be spacing between them as a result of whitespace

    • The solution is to refactor the HTML to remove newlines or whitespace characters between images

    • HTML is space-sensitive, the browser can't tell if whitespace has been added to separate words in a paragraph or to indent the HTML (this issue is specific to flow layout only)

    <img
        src="URL"
        width="100"
        alt="Description"
    />
    <img
        src="URL"
        width="100"
        alt="Description"
    />
    <img
        src="URL"
        width="100"
        alt="Description"
    />
    
    <img src="URL" width="100" alt="Description"/>
    <img src="URL" width="100" alt="Description"/>
    <img src="URL" width="100" alt="Description"/>
    
    • They can also line-wrap, which explains why a vertical-margin property is useless on inline elements
  • An inline-block element is a block-level element that can be placed in an inline context

    • In terms of layout, it's treated as an inline element
    • Under the hood, it's treated more like a block element
      • Properties like width or margin-top have effect now whereas for inline elements, there was no effect
    • A downside is line-wrapping is disabled

Width Algorithms

  • Block elements have dynamic sizing, with a default width of auto, not 100%

    • width: auto is similar to margin: auto, taking as much available space as possible
    • The following <h1> will take up (100% - 16px - 16px) pixels
    h1 {
        width: 100%;
        margin: 0 16px;
    }
    
  • Measurements

    • Percentage: Size is relative to the parent element's available space
    • Explicit: Fixed size (px, em, rem)
    • Keywords: Specify different behaviours depending on the context
  • min-content property specifies the element to become as narrow as possible based on child elements

    h1 {
        width: min-content;     /* Shrunk to just barely fit the text */
    }
    
    • max-content is similar in principle, but it will shrink the element to the smallest width possible to fit text without any line-breaks
    • fit-content factors in the parent container, adding line-breaks if necessary but will always consider selecting the smallest possible width
  • Constrain element sizes using min-width and max-width properties

  • The following is generic utility class to constrain an element to sit in the middle of a column

    • Fills available space on smaller viewports
    • Sets maximum width and horizontally centre itself within the parent if there is remaining space
    • Horizontal padding so the children elements aren't compressed into the edges
    <style>
        .max-width-wrapper {
            max-width: 350px;
            margin-left: auto;
            margin-right: auto;
            padding-left: 16px;
            padding-right: 16px;
    }
    </style>
    
    <div class="max-width-wrapper">
        <div class="card">
            <p>
                ...
            </p>
        </div>
    </div>
    
  • <figure> tag is quite useful for displaying content like images, videos, code, etc. but allows a caption to be added

    • <figcaption> allows that content to be captioned
    <figure>
        <img
            src="URL"
            alt="Description"
        />
        <figcaption>
            Source: ...
        </figcaption>
    </figure>
    

Height Algorithms

  • Compared to the default width behaviour, which tries to maximise the filled space, the default height behaviour tries to be as small as possible

    • It's closer to width: min-content than width: auto
    • Height is more dynamic than width, as more content fills a container, it's expected to grow taller on phone screens and shorter on monitors
  • Consider a page that doesn't have much content but we want it to take up at least 100% of the available space

    • min-height: 100% doesn't work because it's based off the parent height, which is the <body> tag in the example
    • <body> doesn't have a specific height, so the default behaviour to stay as short as possible is used
    <style>
        .wrapper {
            min-height: 100%;
        }
    </style>
    
    <section class="wrapper">
        <p>
            ...
        </p>
    </section>
    
    • The solution is for the parent elements of wrapper to have a height of 100%, so its min-height value is equal to the viewport height

      • Put height: 100% on every element before the main one (including html and body)
      • Put min-height: 100% on the wrapper
      • Don't use any percentage-based heights within that wrapper
      /* Some gotchas if you're using React and Next.js, apply height: 100% to these wrappers too */
      
      html,
      body,
      #root,              /* For create-react-app */
      #__next {           /* For Next.js */
          height: 100%;
      }
      
      • The vh unit, designed so the element will inherit its height from the viewport size
      • Unfortunately, not as practical because there are issues with mobile devices

Margin Collapse

  • Margin collapse is unique to flow layout, not flexbox, grid, or any other layout

  • Only vertical margins collapse

    • Here, the paragraphs have 24px of vertical margin and that margin collapses. The paragraphs will be 24px apart, not 48px
    <style>
        p {
            margin-top: 24px;
            margin-bottom: 24px;
        }
    </style>
    
    <p>Paragraph One</p>
    <p>Paragraph Two</p>
    
    • Horizontal margins don't collapse, due to the objective of CSS in the early days focusing mainly on headers and paragraphs primarily
  • Only adjacent elements can collapse

    • Modifying the HTML above to add a line-break means the paragraphs are no longer adjacent, and therefore, don't collapse
    <p>Paragraph One</p>
    <br />
    <p>Paragraph Two</p>
    
  • If two elements have different margin-bottom and margin-top sizes, then the larger size will take precedence

  • Nesting doesn't prevent collapsing

    • Margin is used to increase distance between sibling elements, not between a child and its parent's bounding box (that's what padding is used for)
    • Here, the margin is transferred to the parent <div> element
    <div>
        <p>Paragraph One</p>
    </div>
    <p>Paragraph Two</p>
    
  • Scenarios where margin collapse can be blocked:

    • Padding and borders
    • Gaps, if a parent element were given a height which caused a gap beneath the child element
    • Scroll container
    • Basically, margins must be touching in order for collapse to occur
  • Margins can also collapse in the same direction

    • The child margin is "absorbed" into the parent margin, combining to use the larger margin
    <style>
        .parent {
            margin-top: 72px;
        }
    
        .child {
            margin-top: 24px;
        }
    </style>
    
    <div class="parent">
        <p class="child">Paragraph One</p>
    </div>
    
  • More than two margins can collapse

    • Siblings can combine adjacent margins (if the first element has margin-bottom, and the second one has margin-top)
    • A parent and child can combine margins in the same direction
    <style>
        header {
            margin-bottom: 10px;
        }
    
        header h1 {
            margin-bottom: 20px;
        }
    
        section {
            margin-top: 30px;
        }
    
        section p {
            margin-top: 40px;
        }
    </style>
    
    <header>
        <h1>My Project</h1>
    </header>
    <section>
        <p>Hello World</p>
    </section>
    
    • The space between <header> and <section> has 4 separate margins competing to occupy that space
      • The header wants space below itself
      • The h1 in the header has a bottom margin, which collapses with its parent
      • The section below the header wants space above itself
      • The p in the section has a top margin, which collapses with its parent
      • Finally, the paragraph has the largest total margin so the value of a 40px margin separates the header and section
  • Collapsing negative margins

    • Works similarly to positive margins, the more negative value "wins" and becomes the overlapping margin
    • If a margin is positive and the other is negative, the values are added together
    • If there are multiple margins that collapse with positive and negative values
      • All positive margins collapse together
      • All negative margins collapse together
      • Add the positive and negative value together to get the overlapping margin
    <style>
        header {
            margin-bottom: -20px;
        }
    
        header h1 {
            margin-bottom: 10px;
        }
    
        section {
            margin-top: -10px;
        }
    
        section p {
            margin-top: 30px;
        }
    </style>
    
    <header>
        <h1>My Project</h1>
    </header>
    <section>
        <p>Hello World</p>
    </section>
    
    • The most positive margin is 30px
    • The most negative margin is -20px
    • Finally, sum those two values to get an overlapping margin of 10px