How Web Apps Work: Browsers, HTML, and CSS

This is a post in the How Web Apps Work series.


An overview of the concepts, terms, and data flow used in web apps: browsers, HTML, and CSS

Web development is a huge field with a vast array of concepts, terms, tools, and technologies. For people just getting started in web dev, this landscape is often bewildering - it's unclear what most of these pieces are, much less how they fit together.

This series provides an overview of fundamental web dev concepts and technologies, what these pieces are, why they're needed, and how they relate to each other. It's not a completely exhaustive reference to everything in web development, nor is it a "how to build apps" guide. Instead, it's a map of the territory, intended to give you a sense of what the landscape looks like, and enough information that you can go research these terms and topics in more depth if needed.

Some of the descriptions will be more oriented towards modern client-side app development with JavaScript, but most of the topics are fundamental enough that they apply to server-centric applications as well.

Other posts in this series cover additional topics, such as:

New terms will be marked in italics. I'll link references for some of them, but encourage you to search for definitions yourself. Also, some of the descriptions will be simplified to avoid taking up too much space or dealing with edge cases.

There's a comprehensive set of HTML and CSS tutorials at Interneting is Hard: HTML and CSS, with beautiful diagrams and great explanations of these terms and concepts. (I've borrowed a few of their excellent diagrams for this post.)

CSS Tricks has some amazing guides to specific aspects of CSS, like their guides for Flexbox and CSS Grid, as well as numerous articles on other CSS and web dev topics.

My JavaScript for Java Developers slides also cover much of this post's content as well.

Table of Contents 🔗︎

Browsers 🔗︎

Any application can make requests using the HTTP protocol. However, web browsers are applications whose primary purpose is to request data using HTTP, format the content, and display it on screen. That content is normally defined using the HTML format, and the appearance of the content is defined using CSS.

A browser's implementation can be divided into several areas:

  • The networking logic that is used to request data
  • The rendering engine that parses fetched content, determines how it should be laid out on screen, processes the styling rules, and actually draws the pixels on screen
  • A JavaScript engine that parses and executes JS code
  • The browser chrome that defines the visible UI around the page content (buttons, menus, address bar, etc)

Different browser implementations may share and reuse implementation pieces, but use their own unique implementations for other sections. For example, several browsers are built on top of the "Blink" rendering engine, like Chrome and Edge, but have very different UI implementations and user-visible features.

Browser Rendering Behavior Variations 🔗︎

Browsers all attempt to implement the same language specifications, but each browser ends up implementing those specifications differently. Most of the time the behavior is the same, but browsers frequently have differences in behavior for the exact same features. This has become less of a problem in recent years, as browser support for major specifications has become more consistent, but browser "quirks" in layout, appearance, and JS support are still a major pain point for web developers.

Browsers can also always try adding new features themselves, but in order for features to be considered "standardized", they typically need to be implemented by at least two different browsers and go through the appropriate web specification committees.

Browser Versions 🔗︎

Each time a browser ships a new version, that version has a fixed understanding of a certain subset of the HTML, CSS, and JS specifications. Since that browser version may stay in wide use for many years, developers need to determine what set of browsers and versions they plan to support when developing a site or application.

Browsers originally had infrequent releases of new versions, and each new version had many significant changes. Today, most modern browsers release new versions frequently (every 6-8 weeks), and each new version only has a few meaningful changes. These browsers are referred to as evergreen browsers, because there's always a new version available and most users will automatically be upgraded to the latest version.

The "Can I Use?" site is an incredibly useful resource that has a database of different HTML, CSS, and JS features, and lists which browser versions support each feature as well as how much worldwide market share those browsers have. This is a valuable tool for deciding whether you can safely use a given feature in your site, based on your target audience. For example, the CSS position: sticky layout feature currently has 95% support worldwide, but does not work in Internet Explorer. So, if your target audience still includes IE11 users, you might not want to use that feature.

Major Browsers 🔗︎

The majority of browser market share belongs to a few major browsers. By far the biggest one is Google Chrome.

The market share of each browser has gone up and down over time. Internet Explorer had a near-monopoly in the early 2000s until Firefox became popular. Chrome took over from both of them, and Safari has become a major factor on mobile. Browser market share also varies significantly in different countries.

The browser market share as of October 2020 looks roughly like this. Note that there are rather different usage percentages for desktop devices vs mobile devices:

Name Company Version Evergreen Rendering Engine Desktop % Mobile %
Chrome Google 86 Yes Blink 70% 63%
Safari Apple 14 No WebKit 8% 24%
Firefox Mozilla 82 Yes Gecko 7% 0.50%
Edge Microsoft 86 Yes Blink 6% 0%
Opera Opera 72 Yes Blink 2% 2%
Samsung Internet Samsung 12 No Blink 0% 6%
Edge (legacy) Microsoft 44 No EdgeHTML 1% 0%
Internet Explorer Microsoft 11 No Trident 2% 0%

Browser Notes 🔗︎

Some notes on the major browsers:

  • Chrome:
    • Built on an open-source core called "Chromium", which is effectively Chrome minus all the actual Google account integration and branding
    • The "Chrome" browser partly got its name because it tried to minimize the amount of "browser chrome" (the term for all the browser UI that surrounds the actual page content)
    • Google has a tendency to announce new web features that only exist in Chrome and yet consider them "final" even if other browsers haven't added them yet
  • Safari:
    • The only actual browser rendering engine allowed by Apple on iOS devices. Any other browsers you see on iOS are actually the Safari core with a different UI skin and branding.
    • Apple tends to be slow adding new features to Safari even after they've been added to other browsers. Apple has also refused to add certain features on the grounds that they might make act as privacy leaks in some way.
  • Firefox:
    • Open-source, and built by the Mozilla organization - it's the only major browser not built by one of the big tech companies
    • Now that many browsers build on top of Chromium/Blink or Webkit, Firefox's "Gecko" engine is the main alternative rendering engine still in meaningful use.
  • Edge:
    • There's been two different variations of the MS Edge browser. The first was actually built entirely by Microsoft, using a cleaned-up and modernized version of the rendering engine previously used in Internet Explorer. Recently, Microsoft stopped developing their own rendering engine, labeled that version of Edge as "legacy", and rebuilt Edge on top of Chromium with their own UI.
  • Internet Explorer:
    • IE was the default browser on Windows up until recently. Many companies still have internal websites that only work correctly in IE, which keeps it as a small but persistent portion of the market
    • IE has "stagnated" twice. IE6 came out in 2001 along with Windows XP, but Microsoft stopped developing it for several years until Firefox and Chrome began taking away market share. Today, IE11 is considered an obsolete browser without many modern capabilities, and many companies are finally dropping support for running their sites in IE11.

HTML 🔗︎

Browsers request data from servers using the HTTP protocol. The standard format that browsers read and use to display data is HyperText Markup Language (HTML). HTML is a plain-text format that uses a hierarchy of nested HTML tags to describe the content, layout, and formatting of a web page.

HTML Tags 🔗︎

An HTML tag is a unique name that is written surrounded by angle brackets, like <html>, <img>, or <p>. Most tags also need to have a corresponding closing tag element, and may contain content inside. For example, a paragraph tag may have text and images inside, and a list tag will have list item tags.

Some tags are also self-closing, such as <img />, <hr />, and <input />.

Tags are not case-sensitive, but are usually written lower-case: <img>, not <IMG>.

Tags may also have attributes, written as attributeName="someValue". Some attributes don't need an actual value - the existence of the attribute name is all that's needed, such as <input disabled>.

The HTML spec defines dozens of different tags, each with a different purpose. Browsers determine what should actually be displayed by parsing these tags.

HTML Page Structure 🔗︎

A standard HTML page structure consists of:

  • an outer <html> tag that contains:
    • an optional <head> tag that may have metadata-related tags inside, as well as links to additional asset files that the browser should download
    • a <body> tag containing the actual content of the page

A basic example would look like:

<html>
  <head>
    <title>My Website</title>
    <link rel="stylesheet" type="text/css" href="styles.css">
    <script type="text/javascript" src="logic.js"></script>
  </head>
  <body>
    <h1>Welcome to My Website</h1>
    <p>
      Here's some <b>bold</b> text, and an image:
    </p>
    <img src="party-parrot.gif" />
  </body>
</html>

HTML tags are normally written with indented spacing for each level of nesting, but that's only needed for readability by people - browsers don't care about that.

HTML pages may also start with a <doctype> tag that declares the HTML version being used by the file, although that isn't necessary.

Common HTML Tags 🔗︎

  • Core tags
    • <html>: The outer container of an HTML page
    • <head>: the header section with metadata
    • <body>: the main content of an HTML page
    • <h1> through <h6>: headers of varying sizes
    • <p>: text paragraphs
    • <div>: a generic block
    • <span>: a generic inline piece of content
    • <img src="some-image.png">: links to an image to be displayed
    • <a href="http://www.example.com">: an anchor tag that may point to another page, or a specific location within this page. Clicking an anchor tells the browser to navigate to that URL.
  • Headers:
    • <title>: Page title, shown in the current tab / browser window
    • <meta>: assorted metadata attributes describing a page
    • <link>: link to assets such as stylesheets
    • <script>: link to JavaScript files, or containing inline JS code
  • Formatting
    • <b>, <i>, <u>: bold, italic, underlined text
    • <blockquote>: represents a formatted quotation
  • Lists
    • <ul>, <ol>: list parents. Unordered lists show bullet points for each list item, ordered lists show numbers.
    • <li>: list items inside of lists
  • Tables
    • <table>: defines a table with rows columns
    • <tr>: a single row in the table
    • <thead>: the header area for the table
    • <th>: header cells in a row in the header
    • <tbody>: the body of the table
    • <td>: a normal cell in a row in the body
  • Forms
    • <form>: a form area with inputs
    • <input>: a user-editable input. Browsers have many types of inputs built in, most of which are used by passing a different type attribute to <input>. Common inputs are:
      • "text": a one-row textbox
      • "checkbox": a clickable true/false checkbox
      • "radio": a clickable button where only one value of a group can be selected at a time
      • "file": allows the user to select and upload a file from the local computer
    • <select>: a dropdown for selecting a single value from a list
    • <option>: a single selectable value in a dropdown
    • <textarea> a multi-line textbox
    • <label>: a descriptive label associated with an input

Some tags do basically the same thing as other tags, but are considered to be more "semantic". Semantic markup is the idea that HTML tags should be meaningfully readable to both other developers and the browser itself. For example, a <div> is a generic block of content. The HTML spec now has tags like <header>, <footer>, <main>, and <section>, which behave the same way display-wise, but are intended to be more meaningful in terms of what content they will contain.

Common Attributes 🔗︎

There are several common attributes that can be applied to any tag:

  • id="someValue": gives a globally unique "ID" name to this element. There can be many IDs in a page, but each unique ID should only exist once in a page at a time
  • class="first second third": a space-separated list of "class names", used to help identify elements for styling and searches
  • title="Some description": descriptive text, usually shown in a tooltip when hovering over the element
  • style="color: red; background-color: blue;": a list of inline style rules to change the display of this element. Uses semi-colon-separated CSS rules
  • alt="Some description": descriptive text, typically supplied for images for use when the image can't be displayed, or read by a screen reader

Other attributes only work for certain tags:

  • <img src="some-image.jpg">: the URL for the image to show
  • <a href="http://www.example.com">: the URL for the link
  • <input type="checkbox">: the type of input element to show

Forms 🔗︎

HTML forms allow users to enter values into the browser, such as typing in text or selecting items from a dropdown. The browser maintains the current value of each input internally as the user interacts with the inputs.

Input elements can be put anywhere, but should normally be put inside a <form> tag, which adds some extra behavior. In particular, forms can be submitted - the browser will automatically gather up the values of each input and send them via HTTP request to the server (normally an HTTP POST request).

A basic form might look like:

<form action="/create-user" method="post"> 
    <ul>
        <li>
            <label for="name">Name:</label>
            <input type="text" id="name" name="user_name" />
        </li>
        <li>
            <label for="state">State</label>
            <select name="state">
              <option value="ca">California</option>
              <option value="oh">Ohio</option>
              <option value="ny">New York</option>
            </select>
        </li>
        <li>
            <label for="msg">Message:</label>
            <textarea id="msg" name="user_message"></textarea>
        </li>
    </ul>
    <button type="submit">Create User</button>
</form>

Side note: a <button> in a form defaults to "submit this form" when clicked. If you want to do something else like handling logic in JavaScript (which is common in UI frameworks like React), you need to declare it as a <button type="button"> instead.

CSS 🔗︎

HTML describes a page's overall structure and layout, and browsers have a default basic appearance for each tag. However, every page needs additional styling information added: colors, fonts, layout, and more. This is done with Cascading Style Sheets (CSS), a declarative language that allows adding styling rules to ased on queries that match some set of HTML elements.

CSS declarations can be added to an HTML page by:

  • Linking a separate CSS file in the <head> of the document
  • Adding a <style> tag to the <head> with CSS declarations inside

Alternately, styling rules can be written directly on an element in the HTML with the style attribute, referred to as inline styles.

CSS Syntax 🔗︎

CSS syntax consists of two major pieces:

  • selectors, which are queries that define what elements will be matched
  • rules, which are the individual styling values that will be assigned to all matching elements

The basic syntax consists of a selector followed by a curly-brace block. The block may contain one or more rules, where each rule looks like property-name: value;

Multiple selectors may be declared in front of a rule block, and again any elements that match any of those selectors will be affected by the style rules.

A basic CSS example might look like:

div.active {
  font-weight: bold;
  font-size: 24px;
  color: red;
  border: 1px solid blue;
}

CSS only allows comments using the same multi-line comment syntax as many other languages: /* comment here */

CSS Selectors 🔗︎

CSS selectors can contain many different pieces that uniquely identify some set of elements. Some of the most common matching pieces are:

  • Specific HTML tag types: by name of the tag, without angle brackets ( h1 )
  • Tags with a given classname: by a . in front of the classname ( .myClassname )
  • Tags with a given ID: by a # in front of the ID value ( #myId )
  • Nesting: adding spaces between selector query pieces ( main form.signup input#firstName )
  • Immediate children: putting a > between parent and child ( ul.items > li )
  • The "universal selector" uses a * to match everything

There are also pseudo-class selectors, which apply an additional modifier to the selector matching, and are usually based on some aspect of the element's behavior. For example, the :hover pseudo-class only matches an element where the mouse is currently hovering, and the :disabled pseudo-class only matches an element that is currently marked as disabled. The :nth-child pseudo-class is commonly used for striping the background color of alternating table rows based on even/odd row numbers, and there's dozens of others as well.

An element's final style is made up of all of the combined rules that apply to it. Children inherit most properties from their parents. A rule html {font-family: Courier New;} would set that font as a default for the whole document, but a nested child could have its own font-family value that would override the value it inherited.

There are rules for selector specificity, which define which values take effect when there are multiple competing rules+properties that apply to a given element. More specific rules will override less specific rules. Inline styles have much higher precedence than styles defined separately. Adding a !important at the end of a rule raises its specificity by a large amount, like color: red !important;.

CSS Styling Rules 🔗︎

There are hundreds of CSS properties that can be modified. They range from very simple (changing the text color) to highly complex (creating multi-point color gradients, defining polygon clip masks). Multi-word CSS property names are hyphenated (background-color, font-weight).

Many CSS properties can be grouped by concept, such as:

  • Element backgrounds: background-color, background-image, background-repeat
  • Borders: border-left, border-color, border-width
  • Fonts: font-family, font-weight, font-size
  • Spacing: margin-top, padding-bottom
  • Sizing: height, width
  • Layout: display, position, overflow, z-index

Different properties can accept different kinds of values, and values can often be given in different units. For example, font-size accepts values using "length units", which can be in pixels (px), size relative to parent ( em), size relative to the <html> element ( rem ), actual display sizes ( in, mm, cm ), and more. Layout widths and heights are often given in percentages ( % ) or pixels.

CSS examples:

div { /* will apply to all div tags */
    color: red; /* text color */
}

div span { /* only spans nested in divs */
    color : blue; /* overrides red color */
    font-weight: bold;
}

#some-id { /* only applies to tag with this ID */
    font-size: 24px;
}

.someClassname { /* any tag with this classname */
    border: 1px solid green;
}

p.first.second { /* only paragraphs with both classnames */
    margin: 5px;
}

div.classA , span.classB { /* applies to either */
    min-width: 50%;
    max-height: 300px;
}

div > p { /* only paras as direct children of divs */
    display : none; /* hide entirely */
}

div.myClass:hover { /* only applies on mouse hover */
    border: 1px dotted orange;
}

Page Layout 🔗︎

There are many factors that affect how elements are positioned in a page, and what their size and shape is compared to other elements. HTML has default layout before defined for each element type, but CSS properties can completely change an element's position and layout behavior.

The Box Model 🔗︎

The default layout behavior is called the box model. Every HTML element is treated as a rectangular box, and falls into one of two categories:

  • block boxes stack downwards vertically
  • inline boxes stack sideways horizontally

For example, <div> tags are blocks. Adding <div /><div /><div /> will produce three elements in a column going down the page. On the other hand, <span> tags are inline. Adding <span /><span /><span /> will lay out all three spans side-by-side.

An element's default behavior can be changed using the various options for the display and position properties. For example, adding display: inline; to a <div> tag will make it behave the same as a <span> normally would.

The size of a given element is made up of several pieces:

  • The actual content
  • Padding: extra spacing that is still "inside" the element
  • Border : extra lines around / "outside" the element
  • Margin: extra spacing that is considered "outside" the element

The "padding and border outside" sizing behavior can sometimes feel unexpected, The behavior can be altered using box-sizing: border-box;, which alters the sizing calculations so that the "total width" of an element includes all of its border and padding as well.

Display and Positioning 🔗︎

The display and position properties can be used to alter how an element's layout behavior works.

The display: block; and display: inline; values force an element to one of the standard layout behaviors. There are also specialized layout behaviors, like Flexbox and CSS Grid (described below).

Elements can be removed from the page completely using display: none;.

The position property takes several values, some of which work in combination with the top/bottom/left/right numeric properties:

  • position: static: the default
  • position: relative: offset the location by some amount
  • position: fixed: stays in a single location on screen, even as the document moves around
  • position: absolute: stays in a single location within the document, without following normal layout flow
  • position: sticky: a newer option that keeps items in place for a while until the page scrolls well past them

Any value other than static creates a new stacking context. Loosely put, any children will be positioned with their top and left values relative to where this element is located.

The z-index property affects how elements "stack" on top of each other when they overlap. (Frankly, the z-index property confuses just about everyone, so I'm not even going to explain more than that here because I'll probably get it wrong, but please research it further.)

Flexbox 🔗︎

Flexbox is a CSS layout algorithm that allows aligning items into rows or columns, with rules for aligning items and wrapping them as they overflow.

A flex parent is defined by adding a display: flex rule to an element. All of the immediate children are then laid out automatically as flex items.

Key properties:

  • flex-direction: the primary layout direction for the flexbox parent (row, column)
  • justify-content: how items are aligned in that direction (start, end, separated)
  • align-items: how items are aligned in the cross direction

Centering items both vertically and horizontally had been an exceptionally difficult task on the web for many years, but Flexbox allows centering things fairly easily, such as display: flex; justify-content: center; align-items: center;.

CSS Grid 🔗︎

CSS Grid is a newer CSS layout algorithm designed for 2D layouts with various numbers of rows and columns.

A grid container is defined by adding a display: grid rule to an element. All of the immediate children are then laid out automatically as grid items.

Grid syntax is extensive, and allows defining sets of columns and rows with varying repeated patterns.

Responsive Screen Size UI 🔗︎

Users may view a site using a wide variety of devices with varying screen sizes: desktops such as 1920x1080, tablets such as 768x1024, or phones such as 375x812.

Designers and devs frequently need to create layouts that look good at many different screen sizes, referred to as responsive design. Rather than make completely different sites, they may add CSS rules that only apply in certain cases, such as when the viewing device screen is in a specific size range. This is normally done using media queries, which are CSS conditions that only apply the selectors and rules inside if the condition matches. The CSS rules typically choose certain screen sizes as breakpoints so that the layout changes if your device is larger or smaller than that size. An example media query and breakpoint setup might look like:

/* Small devices (portrait tablets and large phones, 600px and up) */
@media only screen and (min-width: 600px) {
  .notice {
    color: red;
  }
}

/* Medium devices (landscape tablets, 768px and up) */
@media only screen and (min-width: 768px) {
  .notice {
    color: green;
  }
}

/* Large devices (laptops/desktops, 992px and up) */
@media only screen and (min-width: 992px) {
  .notice {
    color: blue;
  }
}

Further Resources 🔗︎


This is a post in the How Web Apps Work series. Other posts in this series: