Jon Rumsey

An online markdown blog and knowledge repository.


Project maintained by nojronatron Hosted on GitHub Pages — Theme by mattgraham

How to Build a Responsive Website without JavaScript

An NDC Oslo presentation titled "Your website does not need JavaScript", NDC Oslo, 2024.

Presenter: Amy Kapernick, Web Developer, Perth, AUS, amykapers

Table of Contents

Non-Building Blocks

What will not be used:

Building-Blocks

What will be used:

Goal: Create an SPA with multiple tabbed pages using Anchor links and CSS.

Use Content Targeting:

<html>
<head>

</head>
<body>
  <header>
    <nav>
      <a href="#home">Home</a>
      ...
      <a href="#contact">Contact</a>
    </nav>
  </header>
  <main>
    <section id="home">
      ...
    </section>
    <section id="contact">
      ...
    </section>
  <main>
</html>
section {
  display: none; // hide this parent element

  &:target { // ampersand causes affect to apply to parent
    display: block; // show the parent element
  }
}

The Target Pseudo Selector:

The Nesting Selector:

Normally, a nested CSS Selector rule would apply to the child of the parent element in the nested CSS. Use of & causes the rule to apply to the parent given the parent element and child attribute/rule setting.

Warning: In practice, this is probably an incomplete solution. For example, a browser first arriving at the website would need to navigate to the website at the #home ID e.g. http://foo.bar/#home (instead of http://foo.bar/ or http://foo.bar/index.html) in order to display any content at all.

Accordians Using the Details Element

<details>
  <summary>Accordian Item Summary</summary>
  <p>Accordian Item content</p>
</details>

Browser Support:

Form Validations

Required Fields:

Data Format:

Browser Support:

CSS Tricks

Nesting

Generally, Pre-Processors are used to make this simple.

Lately, CSS has enabled nesting to enable nesting.

Support:

Range Slider

Apply these attributes to input elements:

The has pseudo-selector:

Amy configured a complicated

Support:

Testimonials

Testimonial Slider: Usually these are rendered as a 'slider' UI, showing multiplt testimonial cards, often automatically.

<input type='radio' id='t_1' name='testimonials' />
<label for='t_1'>Quote 1</label>
<blockquote>
  <!-- quote -->
</blockquote>

Use Input type radio to make a selectable UI element.

Apply CSS to the testimonials element:

.testimonials {
  display: grid; // layout
  grid-template-rows: 1fr auto; // resizable sections
  grid-template-columns: 1rf repeater(var(--num_test), auto) 1ft; // ad spacing for each column data
  justify-content: center;

  &:has(blockquote:nth-of-type(1)) { // count quotes
    --num_test: 1;
  }
  ...
}

blockquote {
  grid-column: 1 / -1; // across end-to-end of display
  grid-row: 1 / 2; // at the end display on the second row
}

label {
  order: 2;
  grid-row: 2;
}

Hide all quotes so they can be selected later:

blockquote {
  ...
  pointer-events: none;
  opacity: 0;
}

input[type='radio']:checked {
  & + label + blockquote {
    opacity: 1;
    pointer-events: auto;
  }
}

Leverage before and after pseudo-elements to label the block quotes nicely:

label {
  ...
  position: relative;
  cursor: pointer;

  &:before, &:after {
    height: 20px;
    width: 20px;
    border-radius: 50%;
    border: 2px solid var(--green);
  }
}

Add "selected" styling to the labels:

label {
  ...
  &:after {
    background: radial-gradient(
      circle, var(--green) calc(100% - 6px),
      transparent calc(100% - 6px)
    );
    position: absolute;
  }
}

Relatively new (less than 5 years) Selectors:

Warnings:

Toggle Switch

This segment was very involved, and included a large amount of CSS (much like the previous topic).

Amy also discussed using the :has pseudo-selector to toggle light and dark mode, using :before and :after selectors in conjunction with :has.

In summary, it was not a great solution because:

An Image Carousel layout can be broken down into 4 elements:

How is this displayed and styled?

.carousel {
  display: grid;
  grid-template-columns: 4em 1fr 4em;
  grid-template-rows: 300px;
  grid-template-areas: 'previous image next';
  overflow: hidden; // do not overflow the page
  max-width: 100%;
}

.image {
  grid-area: image; // see grid-template-areas values
  opacity: 0;
}

label {
  display: none;
}

Find a radio input that is checked, the label after it, and the image class after that, and set the opacity to '1':

input[type='radio']:checked + label + .image {
  opacity: 1;
}

Check if none of the radios have been checked within the carousel, and get the radio input after that, the label after that, and the image after that, and set its opacity to '1':

.carousel:not(:has(input[type='radio']:checked)) {
  & input[type='radio']:first-of-type + label + .image {
    opacity: 1;
  }
}

Add Controls by finding the radio button that is checked, get the label next to it, get the image next to that, then get the radio button next to that and the label next to that, assign its display to block, set grid-area to 'next', and add the :arrow-right: emoji as the content:

inptu[type='radio']:checked + label + .image + input[type='radio'] + label {
  display: block;
  grid-area: next;
  
  &::before {
    content: ':arrow-right:';
  }
}

Do a similar thing to find the 'previous image' to place the :arrow-left: emoji.

Support and Best Pracrtices:

Instead: Use Gallery or something other than an Image Carousel.

Items to Review

Final Comments

JS is useful, but is it really necessary?

References

Check out Amy's live site at nojs.amyskapers.dev.

To learn more about Amy and her talks, see Kapers.dev.