All lessons
FrontendBeginner14 min readIn my stack

HTML, CSS & JavaScript

The foundation of every website: structure, style and behaviour, done properly.

  • Semantic HTML
  • Flexbox & Grid
  • DOM
  • Events
  • fetch
Step 1/5
HTML is the structure: walls, a roof, a door and windows. It says what things are, not how they look.

In 30 seconds

Every web page is built from three layers. HTML describes what the content is (a heading, a button, a form), CSS decides how it looks and how it adapts to every screen, and JavaScript adds behaviour such as reacting to clicks or loading data. Frameworks like React and Next.js sit on top of these three; understanding them well makes you faster and your sites more accessible in any framework.

Why I use it

  • I build every interface, whatever the framework, on semantic HTML and plain CSS principles, because that is what browsers, search engines and screen readers actually understand.
  • I find that most 'framework bugs' I debug are really HTML, CSS or DOM questions: a missing label, a collapsing margin, an event bubbling where nobody expected.
  • I design layouts mobile-first with Flexbox, Grid and design tokens, and check them at phone, tablet and desktop widths before calling a page done.
  • I treat accessibility as part of quality, not an extra: semantic markup costs nothing and helps every user.

How it works

01Semantic HTML and accessibility basics

HTML elements carry meaning. A <button> is focusable, works with Enter and Space, and is announced as a button by screen readers; a <div> with a click handler is none of those things. Use landmarks (<header>, <nav>, <main>, <footer>), one <h1> per page with headings in order, <a> for navigation and <button> for actions, and lists for lists. Every form field needs a visible <label> connected by for and id. Images need an alt text that describes their purpose, or alt="" if they are decorative. Make sure everything works with the keyboard alone and that the focus outline is visible. Aim for WCAG 2.2 level AA, which includes a contrast ratio of at least 4.5:1 for normal text. Use ARIA only when no native element fits: the first rule of ARIA is to prefer native HTML.

html
<header>
  <nav aria-label="Main">
    <a href="/">Home</a>
    <a href="/shop" aria-current="page">Shop</a>
  </nav>
</header>
<main>
  <h1>Running shoes</h1>
  <img src="/img/shoe.webp" alt="Blue running shoe, side view" width="640" height="480" />
  <form action="/subscribe" method="post">
    <label for="email">Email</label>
    <input id="email" name="email" type="email" autocomplete="email" required />
    <button type="submit">Subscribe</button>
  </form>
</main>

02The box model and the cascade

Every element is a rectangular box with four layers: content, padding (space inside the border), border, and margin (space outside). By default, width applies only to the content, so adding padding makes the box wider than you asked for. Setting box-sizing: border-box on everything makes width include padding and border, which is far more intuitive. Vertical margins between blocks can collapse into one, a classic surprise; gap in Flexbox and Grid avoids it. When several rules target the same element, the cascade decides who wins: importance, then specificity (an id beats a class, a class beats an element), then source order. Keep specificity low and flat, mostly single classes, and avoid !important. Modern CSS also offers custom properties (variables) for design tokens, cascade layers (@layer) to order whole groups of styles, and native nesting.

css
:root {
  --space-2: 0.5rem;
  --space-4: 1rem;
  --radius: 12px;
  --color-surface: #ffffff;
}
*, *::before, *::after {
  box-sizing: border-box;
}
.card {
  padding: var(--space-4);
  border: 1px solid #dddddd;
  border-radius: var(--radius);
  background: var(--color-surface);
}
 
.card:focus-visible {
  outline: 2px solid #4f46e5;
  outline-offset: 2px;
}

03Flexbox vs Grid

Both are layout systems, and modern sites use both. Flexbox is one-dimensional: it arranges items in a single row or column and is excellent at distributing space and aligning things, such as a navbar, a button with an icon, or a row of tags that wraps. You think content-first: items size themselves and Flexbox shares the leftover space. Grid is two-dimensional: you define rows and columns, and items are placed into that structure, which suits page layouts, card galleries and dashboards. You think layout-first. A useful Grid pattern is repeat(auto-fill, minmax(240px, 1fr)), which creates as many columns as fit, with no media queries at all. Both support gap for consistent spacing. A practical rule: Grid for the overall structure, Flexbox for components inside it.

css
/* Flexbox: a toolbar with items on both ends */
.toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
 
/* Grid: responsive cards without media queries */
.products {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 1.5rem;
}

04Responsive design

A responsive site works on any screen, from a 360-pixel phone to a wide monitor. Start with the viewport meta tag, or mobile browsers will render a zoomed-out desktop page. Design mobile-first: write the base styles for small screens and add media queries for larger ones, choosing breakpoints where your content breaks, not for specific devices. Use relative units (rem for type and spacing, % and fr for widths) and clamp() for fluid font sizes. Container queries, now supported in all modern browsers, let a component adapt to the width of its parent instead of the whole window, which is ideal for reusable cards. For images, use srcset and sizes so phones download small files, set width and height to prevent layout shift, and lazy-load images below the fold. Test on real devices and with the browser's device toolbar.

html
<meta name="viewport" content="width=device-width, initial-scale=1" />
 
<style>
  h1 { font-size: clamp(1.75rem, 1.2rem + 2.5vw, 3rem); }
 
  .layout { display: grid; gap: 1rem; }
  @media (min-width: 768px) {
    .layout { grid-template-columns: 240px 1fr; }
  }
 
  .card-wrap { container-type: inline-size; }
  @container (min-width: 420px) {
    .card { display: flex; gap: 1rem; }
  }
</style>
 
<img src="/img/hero-800.webp" srcset="/img/hero-400.webp 400w, /img/hero-800.webp 800w, /img/hero-1600.webp 1600w"
     sizes="(min-width: 768px) 50vw, 100vw" width="800" height="450" alt="Product on a desk" loading="lazy" />

05The DOM and events

When the browser parses HTML it builds the DOM (Document Object Model), a tree of objects that JavaScript can read and change. You find elements with querySelector, change them through properties like textContent, classList and dataset, and react to the user with addEventListener. Events travel: a click starts at the target element and bubbles up through its ancestors. That enables event delegation, one listener on a parent that handles clicks for all its children, including ones added later. event.preventDefault() stops the browser's default action, such as submitting a form; event.target tells you what was actually clicked. Use textContent rather than innerHTML for user data to avoid cross-site scripting (XSS). Load scripts with defer or type="module" so they do not block the page from rendering.

html
<ul id="cart">
  <li>Coffee beans <button data-id="101" class="remove">Remove</button></li>
  <li>Filter papers <button data-id="102" class="remove">Remove</button></li>
</ul>
<p id="status" role="status"></p>
 
<script type="module">
  const cart = document.querySelector("#cart");
  const status = document.querySelector("#status");
 
  // One listener for every current and future button (delegation)
  cart.addEventListener("click", (event) => {
    const button = event.target.closest("button.remove");
    if (!button) return;
    button.closest("li").remove();
    status.textContent = `Item ${button.dataset.id} removed`; // safe: no innerHTML
  });
</script>

06Loading data with fetch

fetch() is the browser's built-in way to make HTTP requests, and it is also available in Node.js. It returns a promise of a Response. Two details trip people up. First, fetch only rejects on network failure: a 404 or 500 still resolves, so always check response.ok or response.status. Second, the body is read separately with response.json() or response.text(), which is also asynchronous. Send JSON with method, a Content-Type header and JSON.stringify for the body. Use AbortController to cancel a request when the user navigates away or types a new search, and AbortSignal.timeout() to avoid waiting forever. Show loading and error states in the UI, and disable a submit button while a request is in flight so users cannot send it twice. Cross-origin requests are governed by CORS, which the server, not the browser code, must allow.

js
async function searchProducts(query, signal) {
  const res = await fetch(
    `https://api.example.com/products?q=${encodeURIComponent(query)}`,
    { headers: { Accept: "application/json" }, signal }
  );
  if (!res.ok) throw new Error(`Request failed: ${res.status}`);
  return res.json();
}
 
let controller;
document.querySelector("#search").addEventListener("input", async (e) => {
  controller?.abort(); // cancel the previous, now outdated request
  controller = new AbortController();
  try {
    const results = await searchProducts(e.target.value, controller.signal);
    console.log(results);
  } catch (err) {
    if (err.name !== "AbortError") console.error(err);
  }
});

Use it when

  • Always: every web framework renders HTML, CSS and JavaScript in the end.
  • Simple sites, landing pages and email-like content, where plain HTML and CSS are fast and need no build step.
  • Progressive enhancement: a form that works without JavaScript and gets nicer with it.
  • Debugging framework apps, where the browser's Elements, Network and Console panels show what really happens.

Skip it when

  • Large interactive applications with lots of shared state, where hand-written DOM code becomes hard to maintain and a component framework (React, Vue, Svelte) pays off.
  • Re-implementing complex widgets (date pickers, comboboxes, modals) from scratch when a well-tested accessible component already exists.

Alternatives

  • Component frameworks (React, Vue, Svelte)

    Describe UI as components with state; they still output HTML, CSS and DOM updates.

  • Utility CSS (Tailwind)

    Style with small classes in the markup instead of separate stylesheets; fast, but you still need to understand the box model, Flexbox and Grid.

  • htmx and server-rendered HTML

    Send HTML from the server and swap fragments, with very little JavaScript; a good fit for admin and CRUD apps.

Best practices

  1. Use native elements first: <button> for actions, <a> for navigation, <label> for every input.
  2. Write mobile-first CSS with design tokens (custom properties) instead of hard-coded colours and sizes.
  3. Use Grid for page structure, Flexbox for components, and gap for spacing.
  4. Give images width, height, alt text and responsive srcset; lazy-load below-the-fold images.
  5. Keep focus visible, test with the keyboard only, and check colour contrast against WCAG 2.2 AA.
  6. Check response.ok after every fetch, show loading and error states, and cancel outdated requests.
  7. Respect prefers-reduced-motion for animations.

Common mistakes

  • Clickable <div>s instead of buttons, which keyboard and screen-reader users cannot operate.
  • Inputs without labels, relying on placeholder text that disappears while typing.
  • Forgetting the viewport meta tag, so the mobile site renders zoomed out.
  • Assuming fetch throws on HTTP errors and showing 'success' after a 500 response.
  • Inserting user input with innerHTML and opening the door to XSS.

Deep dive

07The rendering pipeline and Core Web Vitals

To draw a page, the browser parses HTML into the DOM and CSS into the CSSOM, combines them into a render tree, calculates layout (the size and position of every box), paints pixels into layers and finally composites those layers on the GPU. CSS in the head blocks rendering, and classic scripts without defer block parsing, which is why critical CSS should be small and scripts deferred. Changing a layout property such as width or top forces layout again, sometimes for the whole page; animating transform and opacity usually only needs compositing, which is far cheaper. Reading a layout value like offsetHeight right after writing styles forces a synchronous layout, and doing that in a loop (layout thrashing) freezes the page. Google's Core Web Vitals turn this into measurable targets at the 75th percentile of real users: Largest Contentful Paint under 2.5 seconds, Interaction to Next Paint (which replaced First Input Delay in March 2024) under 200 milliseconds, and Cumulative Layout Shift under 0.1.

js
// Layout thrashing: read, write, read, write... forces layout each time
for (const card of cards) {
  card.style.height = card.offsetWidth * 0.75 + "px";
}
 
// Better: batch all reads, then all writes
const widths = cards.map((card) => card.offsetWidth);
cards.forEach((card, i) => {
  card.style.height = widths[i] * 0.75 + "px";
});
 
// Best for this case: let CSS do it, no JavaScript at all
// .card { aspect-ratio: 4 / 3; }
 
// Yield to the browser during long work so input stays responsive (INP)
await new Promise((resolve) => setTimeout(resolve, 0));

08Modern CSS architecture: layers, :where(), :has() and specificity control

Large stylesheets fail because of specificity wars: each fix needs a stronger selector than the last, until !important is everywhere. Modern CSS gives tools to control this deliberately. Cascade layers (@layer reset, base, components, utilities) decide priority by layer order before specificity is even compared, so a simple utility class in a later layer beats a complex component selector in an earlier one. :where() matches like :is() but contributes zero specificity, which is perfect for resets and library styles that should be easy to override. :has() is the long-awaited parent selector: style a card that contains an image, or a form field whose input is invalid, without JavaScript. Custom properties inherit through the tree and can be changed at runtime, so a theme is just a different set of values on a parent element. Together, these let you keep almost every selector at a single class while still having predictable overrides.

css
@layer reset, base, components, utilities;
 
@layer reset {
  :where(ul, ol) { margin: 0; padding: 0; list-style: none; }
}
@layer components {
  .card { padding: var(--space-4); border-radius: var(--radius); }
  .card:has(> img) { padding-top: 0; }               /* parent selector */
  .field:has(input:user-invalid) { --border: #b91c1c; }
}
@layer utilities {
  .p-0 { padding: 0; } /* wins over .card because of layer order */
}
 
[data-theme="dark"] { --color-surface: #111318; --color-text: #e8e8ea; }
 
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; transition: none !important; }
}

09Native platform UI: dialog, popover, inert and focus management

Modals, menus and tooltips used to require hundreds of lines of JavaScript to be accessible. The platform now does much of it. The <dialog> element opened with showModal() traps focus, makes the rest of the page inert, closes on Escape and renders above everything in the top layer. The Popover API (the popover attribute with popovertarget on a button, Baseline since 2024) creates light-dismiss popups such as menus without any script. The inert attribute removes a whole subtree from focus and the accessibility tree, useful for off-canvas navigation. You still own focus management: when a dialog closes, focus should return to the button that opened it (dialog does this for you), and after client-side navigation, move focus to the new page heading so screen-reader users know the content changed. Announce asynchronous results through a live region (role="status" or aria-live) rather than relying on visual changes alone.

html
<button popovertarget="account-menu">Account</button>
<div id="account-menu" popover>
  <a href="/orders">Orders</a>
  <a href="/settings">Settings</a>
</div>
 
<button id="open-delete">Delete address</button>
<dialog id="confirm-delete" aria-labelledby="confirm-title">
  <h2 id="confirm-title">Delete this address?</h2>
  <form method="dialog">
    <button value="cancel">Cancel</button>
    <button value="confirm">Delete</button>
  </form>
</dialog>
<script type="module">
  const dialog = document.querySelector("#confirm-delete");
  document.querySelector("#open-delete").addEventListener("click", () => dialog.showModal());
  dialog.addEventListener("close", () => console.log(dialog.returnValue));
</script>

10Front-end security: XSS, CSP and safe third-party scripts

Cross-site scripting (XSS) happens when attacker-controlled text is interpreted as code: inserted with innerHTML, placed into an href as javascript:, or concatenated into an inline event handler. Escaping by default (textContent, framework templating) prevents most of it; when you truly must render HTML from users, such as rich-text comments, sanitise it with a maintained library like DOMPurify. A Content Security Policy is the second line of defence: an HTTP header that tells the browser which script sources are allowed, so injected scripts do not run. A strict policy uses a per-request nonce or hashes instead of whitelisting whole domains. Trusted Types, where supported, go further by forcing all dangerous DOM sinks through a reviewed policy. Every third-party script you add runs with full access to your page, including form fields, so load as few as possible, pin versions, use Subresource Integrity for static files from CDNs, and never put analytics on payment pages.

http
# HTTP response header (one line in practice), nonce generated per request
Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-r4nd0m123' 'strict-dynamic';
  style-src 'self';
  img-src 'self' data: https://cdn.example.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
 
<!-- Static library from a CDN, verified by hash -->
<script src="https://cdn.example.com/[email protected]/lib.min.js"
        integrity="sha384-BASE64_HASH_OF_THE_FILE" crossorigin="anonymous" defer></script>

Real-world scenarios

01

The page jumps while loading and users tap the wrong button

Problem

On mobile, the product page shifts several times while loading: a banner appears, fonts swap, and images push content down. Users complain about accidental taps, and CLS in field data is 0.32.

How to solve it

Find the shifting elements first: the Performance panel in Chrome DevTools marks layout shifts, and the Web Vitals extension shows which nodes moved. Then reserve space for everything that arrives late. Give every image width and height, or aspect-ratio in CSS. Reserve a fixed min-height for the banner slot, or render it below the fold. Use font-display: swap with a size-adjusted fallback font (size-adjust or a framework font loader) so text does not reflow. Never insert content above existing content unless it responds to a user action. Deploy, then watch the 75th-percentile CLS in real-user data, since lab tests miss shifts caused by ads and slow networks.

02

An accessibility audit fails the checkout

Problem

A client's audit reports that the checkout cannot be completed with a keyboard, error messages are not announced, and contrast fails on several buttons. A legal deadline is a month away.

How to solve it

Fix by impact, starting with blockers. Replace clickable divs with real buttons and links, make sure every step works with Tab, Enter, Space and Escape, and keep a visible :focus-visible outline. Link every input to a label, mark errors with aria-invalid and connect the message with aria-describedby; on submit, move focus to an error summary with links to the fields. Announce async results like 'card declined' in a role="alert" region. Fix colour tokens once so every button passes 4.5:1. Verify with axe DevTools, then test manually with a screen reader such as VoiceOver or NVDA, because automated tools catch only part of the issues.

03

A marketing script slows every interaction

Problem

After adding chat, heatmap and two tracking scripts, the site feels sluggish. INP in field data rose to 450 ms, and the main thread is busy for seconds after load.

How to solve it

Profile in the Chrome Performance panel with CPU throttling and group activity by third party to see which script owns the long tasks. Remove scripts nobody uses; there usually is one. Load the rest with defer or after the load event, and load the chat widget only when the user clicks a 'Chat' button (a facade). Move tag-manager tags behind consent, which is required anyway. In your own code, break long handlers into smaller chunks, yield to the main thread between them, and keep heavy work like filtering big lists out of input handlers. Recheck INP in real-user monitoring after a few days, since it is a field metric.

Cheat sheet

Modal with focus trap and Escape
dialog.showModal() // <form method="dialog"> closes it
Menu without JavaScript
<button popovertarget="menu">Menu</button> <div id="menu" popover>...</div>
Hide a region from focus and screen readers
<aside inert>...</aside>
Order the cascade
@layer reset, base, components, utilities;
Zero-specificity reset
:where(ul, ol) { list-style: none; }
Style a parent by its child
.card:has(> img) { padding-top: 0; }
Reserve space for media
img, video { aspect-ratio: 16 / 9; width: 100%; height: auto; }
Announce async updates
<p role="status" aria-live="polite"></p>
Reduced motion
@media (prefers-reduced-motion: reduce) { ... }
Basic CSP header
Content-Security-Policy: default-src 'self'; frame-ancestors 'none'

In my real projects

  • I rebuilt an agency's marketing website from the ground up, including interactive effects, dark mode, sitemap and SEO redirects, on top of these fundamentals.
  • In the pet-supplies storefront I wrote the design-system rules down for the whole team: design tokens only, no raw colour or pixel values, and seven defined breakpoints.
  • I check pages at phone, tablet and desktop widths (390, 768 and 1920 pixels) with an automated browser-verification step before work is accepted.
  • Our in-house packages include a design inspector that lets designers adjust CSS variables live in the browser and save them back to the source.

Questions & answers

Q1What is semantic HTML and why does it matter?

Semantic HTML means choosing elements for their meaning, not their look: <nav> for navigation, <button> for actions, <h2> for a section heading, <table> for tabular data. The browser and assistive technologies use that meaning. A screen reader can list headings and landmarks so users jump around the page; a real button is keyboard-focusable and announced correctly with no extra code; search engines understand the structure and main content. It also reduces code, because you get behaviour for free instead of rebuilding it with JavaScript and ARIA. Styling is separate, so a semantic element can look like anything you want.

Q2When do you choose Flexbox and when Grid?

Flexbox is for one dimension: a row or a column of items whose size comes mostly from their content, such as navigation bars, toolbars, button groups or tag lists that wrap. Grid is for two dimensions: when you want to define rows and columns first and place items into them, such as page layouts, product galleries and dashboards. Grid also gives responsive columns without media queries via repeat(auto-fill, minmax(...)). In practice I use both on every page: Grid for the overall structure and card lists, Flexbox for alignment inside each component. Both support gap, which I prefer to margins between items.

Q3Explain event bubbling and event delegation.

When you click an element, the event first travels down from the document to the target (the capture phase), then bubbles back up through every ancestor. Listeners on parents therefore hear clicks on their children. Event delegation uses this: instead of attaching a listener to every row of a long list, you attach one listener to the list and check event.target, usually with closest() to find the relevant child. It uses less memory, and it automatically works for items added later. You can stop propagation with stopPropagation(), but use that sparingly, because it breaks other code, such as analytics or outside-click handlers, that relies on bubbling.

Q4What does box-sizing: border-box change?

By default (content-box), width and height set only the content area; padding and border are added on top. A 300-pixel-wide box with 20 pixels of padding and a 1-pixel border is really 342 pixels wide, which breaks layouts in confusing ways. With border-box, width includes padding and border, so the box is exactly 300 pixels and the content shrinks to fit. That matches how designers think and makes percentage-based layouts predictable. Almost every modern reset applies border-box to all elements and pseudo-elements, and I do the same in every project.

Q5Why doesn't fetch throw an error on a 404 or 500 response?

Because, from fetch's point of view, the request succeeded: it reached the server and received a valid HTTP response. fetch only rejects when the request could not be completed at all, for example when the network is down, DNS fails, CORS blocks it or it was aborted. HTTP error statuses are normal responses that you must check yourself with response.ok, which is true for 200-299, or response.status. I wrap fetch in a small helper that checks ok, parses JSON, throws a typed error with the status and body, and supports an AbortSignal and timeout, so the rest of the code never forgets these checks.

Senior-level questions

S1How would you build a design system that works across several products and frameworks?

I separate it into layers with different lifetimes. The base is design tokens, colours, spacing, type, radii and motion, stored as data and exported as CSS custom properties, so any framework or even plain HTML can use them and themes are just different values. On top come CSS conventions: cascade layers, low-specificity class names and documented breakpoints. Components come last and are the most framework-specific, built on native elements for accessibility. The trade-off is between flexibility and consistency: strict components keep products uniform but frustrate teams with special needs, so I allow composition and documented escape hatches rather than endless props. Versioning matters too: tokens change rarely and safely, while component APIs need semver and migration notes, so I release them separately where possible.

S2A page has poor INP. How do you diagnose and fix it?

INP measures the delay from an interaction to the next frame, so the cause is always the main thread being busy: before the handler runs (input delay), in the handler itself (processing), or in rendering afterwards (presentation delay). I start with field data, using real-user monitoring or the web-vitals library with attribution, to find which interactions and elements are slow, because lab tests rarely reproduce them. Then I reproduce with CPU throttling in the Performance panel. Fixes depend on the phase: remove or delay third-party scripts that create long tasks; split long handlers and yield between chunks; update the UI first and do expensive work afterwards; avoid layout thrashing; reduce DOM size and use content-visibility for long off-screen sections. In React apps, transitions and smaller re-render scopes help the same way.

S3When is progressive enhancement worth the extra effort in 2026?

It is worth it wherever failure is expensive and JavaScript can fail: checkout, sign-up, search and contact forms, and content that must be indexed. JavaScript fails more often than teams think: slow networks time out, extensions break scripts, old devices run out of memory, and a single runtime error can stop a whole bundle. A form that submits as plain HTML and is enhanced with client-side validation and instant feedback keeps working in all those cases. Modern frameworks make this cheaper than it used to be, because server actions and native form submission work without hydration. The trade-off is that highly interactive tools, such as editors, maps or dashboards, gain little from a no-JavaScript fallback, so I apply progressive enhancement to critical paths rather than as a blanket rule.

Glossary

Render tree
The combination of DOM and CSSOM that the browser lays out and paints; hidden elements are excluded.
Reflow (layout)
Recalculating sizes and positions of boxes after a change; expensive when it affects large parts of the page.
Compositing
Combining painted layers on the GPU; animating transform and opacity usually only needs this step.
LCP
Largest Contentful Paint: when the biggest visible element finishes rendering. Good is under 2.5 seconds.
INP
Interaction to Next Paint: how quickly the page visibly responds to clicks, taps and keys. Good is under 200 ms.
CLS
Cumulative Layout Shift: how much visible content moves unexpectedly while the page is used. Good is under 0.1.
Cascade layer
A named group of styles; layer order decides priority before selector specificity is compared.
Top layer
A browser-managed layer above all content where modal dialogs and popovers render, regardless of z-index.
CSP
Content Security Policy: a response header listing which sources of scripts, styles and frames the browser may load.
Live region
An element whose content changes are announced by screen readers, marked with aria-live or role="status".

Have a product in mind?Let's build it.

Tell me about your idea, your product or your team. I usually reply within one working day.