progressive enhancement – CSS-Tricks https://css-tricks.com Tips, Tricks, and Techniques on using Cascading Style Sheets. Fri, 02 Aug 2024 16:49:18 +0000 en-US hourly 1 https://wordpress.org/?v=6.6.1 https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&ssl=1 progressive enhancement – CSS-Tricks https://css-tricks.com 32 32 45537868 HTML Web Components Make Progressive Enhancement and CSS Encapsulation Easier! https://css-tricks.com/html-web-components-make-progressive-enhancement-and-css-encapsulation-easier/ https://css-tricks.com/html-web-components-make-progressive-enhancement-and-css-encapsulation-easier/#comments Thu, 01 Aug 2024 13:21:37 +0000 https://css-tricks.com/?p=379335 I have to thank Jeremy Keith and his wonderfully insightful article from late last year that introduced me to the concept of HTML Web Components. This was the “a-ha!” moment for me:

When you wrap some existing markup in a


HTML Web Components Make Progressive Enhancement and CSS Encapsulation Easier! originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I have to thank Jeremy Keith and his wonderfully insightful article from late last year that introduced me to the concept of HTML Web Components. This was the “a-ha!” moment for me:

When you wrap some existing markup in a custom element and then apply some new behaviour with JavaScript, technically you’re not doing anything you couldn’t have done before with some DOM traversal and event handling. But it’s less fragile to do it with a web component. It’s portable. It obeys the single responsibility principle. It only does one thing but it does it well.

Until then, I’d been under the false assumption that all web components rely solely on the presence of JavaScript in conjunction with the rather scary-sounding Shadow DOM. While it is indeed possible to author web components this way, there is yet another way. A better way, perhaps? Especially if you, like me, advocate for progressive enhancement. HTML Web Components are, after all, just HTML.

While it’s outside the exact scope of what we’re discussing here, Adny Bell has a recent write-up that offers his (excellent) take on what progressive enhancement means.

Let’s look at three specific examples that show off what I think are the key features of HTML Web Components — CSS style encapsulation and opportunities for progressive enhancement — without being forced to depend on JavaScript to work out of the box. We will most definitely use JavaScript, but the components ought to work without it.

The examples can all be found in my Web UI Boilerplate component library (built using Storybook), along with the associated source code in GitHub.

Example 1: <webui-disclosure>

Storybook render of webui-disclosure Web Component.q
Live demo

I really like how Chris Ferdinandi teaches building a web component from scratch, using a disclosure (show/hide) pattern as an example. This first example extends his demo.

Let’s start with the first-class citizen, HTML. Web components allow us to establish custom elements with our own naming, which is the case in this example with a <webui-disclosure> tag we’re using to hold a <button> designed to show/hide a block of text and a <div> that holds the <p> of text we want to show and hide.

<webui-disclosure
  data-bind-escape-key
  data-bind-click-outside
>
  <button
    type="button"
    class="button button--text"
    data-trigger
    hidden
  >
    Show / Hide
  </button>

  <div data-content>
    <p>Content to be shown/hidden.</p>
  </div>
</webui-disclosure>

If JavaScript is disabled or doesn’t execute (for any number of possible reasons), the button is hidden by default — thanks to the hidden attribute on it— and the content inside of the div is simply displayed by default.

Nice. That’s a really simple example of progressive enhancement at work. A visitor can view the content with or without the <button>.

I mentioned that this example extends Chris Ferdinandi’s initial demo. The key difference is that you can close the element either by clicking the keyboard’s ESC key or clicking anywhere outside the element. That’s what the two [data-attribute]s on the <webui-disclosure tag are for.

We start by defining the custom element so that the browser knows what to do with our made-up tag name:

customElements.define('webui-disclosure', WebUIDisclosure);

Custom elements must be named with a dashed-ident, such as <my-pizza> or whatever, but as Jim Neilsen notes, by way of Scott Jehl, that doesn’t exactly mean that the dash has to go between two words.

I typically prefer using TypeScript for writing JavaScript to help eliminate stupid errors and enforce some degree of “defensive” programming. But for the sake of simplicity, the structure of the web component’s ES Module looks like this in plain JavaScript:

default class WebUIDisclosure extends HTMLElement {
  constructor() {
    super();
    this.trigger = this.querySelector('[data-trigger]');
    this.content = this.querySelector('[data-content]');
    this.bindEscapeKey = this.hasAttribute('data-bind-escape-key');
    this.bindClickOutside = this.hasAttribute('data-bind-click-outside');
    
    if (!this.trigger || !this.content) return;
    
    this.setupA11y();
    this.trigger?.addEventListener('click', this);
  }

  setupA11y() {
    // Add ARIA props/state to button.
  }

  // Handle constructor() event listeners.
  handleEvent(e) {
    // 1. Toggle visibility of content.
    // 2. Toggle ARIA expanded state on button.
  }
  
  // Handle event listeners which are not part of this Web Component.
  connectedCallback() {
    document.addEventListener('keyup', (e) => {
      // Handle ESC key.
    });
  
    document.addEventListener('click', (e) => {
      // Handle clicking outside.
    });
  }

  disconnectedCallback() {
    // Remove event listeners.
  }
}

Are you wondering about those event listeners? The first one is defined in the constructor() function, while the rest are in the connectedCallback() function. Hawk Ticehurst explains the rationale much more eloquently than I can.

This JavaScript isn’t required for the web component to “work” but it does sprinkle in some nice functionality, not to mention accessibility considerations, to help with the progressive enhancement that allows the <button> to show and hide the content. For example, JavaScript injects the appropriate aria-expanded and aria-controls attributes enabling those who rely on screen readers to understand the button’s purpose.

That’s the progressive enhancement piece to this example.

For simplicity, I have not written any additional CSS for this component. The styling you see is simply inherited from existing global scope or component styles (e.g., typography and button).

However, the next example does have some extra scoped CSS.

Example 2: <webui-tabs>

That first example lays out the progressive enhancement benefits of HTML Web Components. Another benefit we get is that CSS styles are encapsulated, which is a fancy way of saying the CSS doesn’t leak out of the component. The styles are scoped purely to the web component and those styles will not conflict with other styles applied to the current page.

Let’s turn to a second example, this time demonstrating the style encapsulating powers of web components and how they support progressive enhancement in user experiences. We’ll be using a tabbed component for organizing content in “panels” that are revealed when a panel’s corresponding tab is clicked — the same sort of thing you’ll find in many component libraries.

Storybook render of the webui-tabs web component.
Live demo

Starting with the HTML structure:

<webui-tabs>
  <div data-tablist>
    <a href="#tab1" data-tab>Tab 1</a>
    <a href="#tab2" data-tab>Tab 2</a>
    <a href="#tab3" data-tab>Tab 3</a>
  </div>

  <div id="tab1" data-tabpanel>
    <p>1 - Lorem ipsum dolor sit amet consectetur.</p>
  </div>

  <div id="tab2" data-tabpanel>
    <p>2 - Lorem ipsum dolor sit amet consectetur.</p>
  </div>

  <div id="tab3" data-tabpanel>
    <p>3 - Lorem ipsum dolor sit amet consectetur.</p>
  </div>
</webui-tabs>

You get the idea: three links styled as tabs that, when clicked, open a tab panel holding content. Note that each [data-tab] in the tab list targets an anchor link matching a tab panel ID, e.g., #tab1, #tab2, etc.

We’ll look at the style encapsulation stuff first since we didn’t go there in the last example. Let’s say the CSS is organized like this:

webui-tabs {
  [data-tablist] {
    /* Default styles without JavaScript */
  }
  
  [data-tab] {
    /* Default styles without JavaScript */
  }

  [role='tablist'] {
    /* Style role added by JavaScript */
  }
  
  [role='tab'] {
    /* Style role added by JavaScript */
  }
  
  [role='tabpanel'] {
    /* Style role added by JavaScript */
  }
}

See what’s happening here? We have two style rules — [data-tablist] and [data-tab] — that contain the web component’s default styles. In other words, these styles are there regardless of whether JavaScript loads or not. Meanwhile, the other three style rules are selectors that are injected into the component as long as JavaScript is enabled and supported. This way, the last three style rules are only applied if JavaScript plops the **role** attribute on those elements in the HTML. Right there, we’re already supplying a touch of progressive enhancement by setting styles only when JavasScript is needed.

All these styles are fully encapsulated, or scoped, to the <webui-tabs> web component. There is no “leakage” so to speak that would bleed into the styles of other web components, or even to anything else on the page within the global scope. We can even choose to forego classnames, complex selectors, and methodologies like BEM in favour of simple descendent selectors for the component’s children, allowing us to write styles more declaratively on semantic elements.

Quickly: “Light” DOM versus Shadow DOM

For most web projects, I generally prefer to bundle CSS (including the web component Sass partials) into a single CSS file so that the component’s default styles are available in the global scope, even if the JavaScript doesn’t execute.

However, it is possible to import a stylesheet via JavaScript that is only consumed by this web component if JavaScript is available:

import styles from './styles.css';

class WebUITabs extends HTMLElement {
  constructor() {
    super();
    this.adoptedStyleSheets = [styles];
  }
}

customElements.define('webui-tabs', WebUITabs);

Alternatively, we could inject a <style> tag containing the component’s styles instead:

class WebUITabs extends HTMLElement {
  connectedCallback() {
    this.attachShadow({ mode: 'open' }); // Required for JavaScript access
    this.shadowRoot.innerHTML = `
      <style> <!-- styles go here --> </style>
      // etc.
    `;
  }
}
    
customElements.define('webui-tabs', WebUITabs);

Whichever method you choose, these styles are scoped directly to the web component, preventing component styles from leaking out, but allowing global styles to be inherited.

Now consider this simple example. Everything we write in between the component’s opening and closing tags is considered part of the “Light” DOM.

<my-web-component>
  <!-- This is Light DOM -->
  <div>
    <p>Some content... styles are inherited from the global scope</p>
  </div>

  ----------- Shadow DOM Boundary -------------
  | <!-- Anything injected by JavaScript -->  |
  ---------------------------------------------
</my-web-component>

Dave Rupert has an excellent write-up that makes it really easy to see how external styles are able to “pierce” the Shadow DOM and select an element in the Light DOM. Notice how the <button> element that is written in between the custom element’s tags receives the button selector’s styles in the global CSS, while the <button> injected via JavaScript is left untouched.

If we want to style the Shadow DOM <button> we’d have to do that with internal styles like the examples above for importing a stylesheet or injecting an inline <style> block.

That doesn’t mean that all CSS style properties are blocked by the Shadow DOM. In fact, Dave outlines 37 properties that web components inherit, mostly along the lines of text, list, and table formatting.

Progressively enhance the tabbed component with JavaScript

Even though this second example is more about style encapsulation, it’s still a good opportunity to see the progressive enhancement we get practically for free from web components. Let’s step into the JavaScript now so we can see how we can support progressive enhancement. The full code is quite lengthy, so I’ve abbreviated things a bit to help make the points a little clearer.

default class WebUITabs extends HTMLElement {
  constructor() {
    super();
    this.tablist = this.querySelector('[data-tablist]');
    this.tabpanels = this.querySelectorAll('[data-tabpanel]');
    this.tabTriggers = this.querySelectorAll('[data-tab]');

    if (
      !this.tablist ||
      this.tabpanels.length === 0 ||
      this.tabTriggers.length === 0
    ) return;
    
    this.createTabs();
    this.tabTriggers.forEach((tabTrigger, index) => {
      tabTrigger.addEventListener('click', (e) => {
        this.bindClickEvent(e);
      });
      tabTrigger.addEventListener('keydown', (e) => {
        this.bindKeyboardEvent(e, index);
      });
    });
  }

  createTabs() {
    // 1. Hide all tabpanels initially.
    // 2. Add ARIA props/state to tabs & tabpanels.
  }

  bindClickEvent(e) {
    e.preventDefault();
    // Show clicked tab and update ARIA props/state.
  }
  bindKeyboardEvent(e, index) {
    e.preventDefault();
    // Handle keyboard ARROW/HOME/END keys.
  }
}

customElements.define('webui-tabs', WebUITabs);

The JavaScript injects ARIA roles, states, and props to the tabs and content blocks for screen reader users, as well as extra keyboard bindings so we can navigate between tabs with the keyboard; for example, the TAB key is reserved for accessing the component’s active tab and any focusable content inside the active tabpanel, and the tabs can be traversed with the ARROW keys. So, if JavaScript fails to load, the default experience is still an accessible one where the tabs still anchor link to their respective panels, and those panels naturally stack vertically, one on top of the other.

And if JavaScript is enabled and supported? We get an enhanced experience, complete with updated accessibility considerations.

Example 3: <webui-ajax-loader>

Storybook render of webui-ajax-loader web component.
Live demo

This final example differs from the previous two in that it is entirely generated by JavaScript, and uses the Shadow DOM. This is because it is only used to indicate a “loading” state for Ajax requests, and is therefore only needed when JavaScript is enabled.

The HTML markup is just the opening and closing component tags:

<webui-ajax-loader></webui-ajax-loader>

The simplified JavaScript structure:

default class WebUIAjaxLoader extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.innerHTML = `
      <svg role="img" part="svg">
        <title>loading</title>
        <circle cx="50" cy="50" r="47" />
      </svg>
    `;
  }
}

customElements.define('webui-ajax-loader',WebUIAjaxLoader);

Notice right out of the gate that everything in between the <webui-ajax-loader> tags is injected with JavaScript, meaning it’s all in the Shadow DOM, encapsulated from other scripts and styles not directly bundled with the component.

But also notice the part attribute that’s set on the <svg> element. Here’s where we’ll zoom in:

<svg role="img" part="svg">
  <!-- etc. -->
</svg>

That’s yet another way we have to style the custom element: named parts. Now we can style that SVG from outside of the template literal we used to establish the element. There’s a ::part pseudo-selector to make that happen:

webui-ajax-loader::part(svg) {
  // Shadow DOM styles for the SVG...
}

And here’s something cool: that selector can access CSS custom properties, whether they are scoped globally or locally to the element.

webui-ajax-loader {
  --fill: orangered;
}

webui-ajax-loader::part(svg) {
  fill: var(--fill);
}

As far as progressive enhancement goes, JavaScript supplies all of the HTML. That means the loader is only rendered if JavaScript is enabled and supported. And when it is, the SVG is added, complete with an accessible title and all.

Wrapping up

That’s it for the examples! What I hope is that you now have the same sort of epiphany that I had when reading Jeremy Keith’s post: HTML Web Components are an HTML-first feature.

Of course, JavaScript does play a big role, but only as big as needed. Need more encapsulation? Want to sprinkle in some UX goodness when a visitor’s browser supports it? That’s what JavaScript is for and that’s what makes HTML Web Components such a great addition to the web platform — they rely on vanilla web languages to do what they were designed to do all along, and without leaning too heavily on one or the other.


HTML Web Components Make Progressive Enhancement and CSS Encapsulation Easier! originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/html-web-components-make-progressive-enhancement-and-css-encapsulation-easier/feed/ 3 379335
The Optional Chaining Operator, “Modern” Browsers, and My Mom https://css-tricks.com/the-optional-chaining-operator-modern-browsers-and-my-mom/ https://css-tricks.com/the-optional-chaining-operator-modern-browsers-and-my-mom/#comments Wed, 02 Feb 2022 01:14:18 +0000 https://css-tricks.com/?p=362890 Jim Nielsen’s mom couldn’t open a website. Jim worked on confirming the issue and documented how he got to the bottom of it:

“[…] well it can’t be a browser issue. It’s not like my Mom is using Internet Explorer!


The Optional Chaining Operator, “Modern” Browsers, and My Mom originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Jim Nielsen’s mom couldn’t open a website. Jim worked on confirming the issue and documented how he got to the bottom of it:

“[…] well it can’t be a browser issue. It’s not like my Mom is using Internet Explorer! She has relatively modern tech: an iPad (Safari) and a Chromebox (Google Chrome).”

But the more I thought about it—a website that works on some devices but not on others—the more I realized this had to be a browser issue.

So I looked at the version of Chrome on my parent’s computer. Version 76! I knew we were at ninety-something in 2022, so I figured that was the culprit. “I’ll just update Chrome,” I thought.

Turns out, you can’t.

I absolutely celebrate the idea of evergreen browsers. It’s one of the absolute most important things that has happened to the web in recent-ish years. It enables a much quicker evolution for the web, and all browsers are taking advantage of it.

But even browsers that I think of as evergreen aren’t always. Eventually, hardware limits the software. The logic isn’t as simple as “if Chrome, then evergreeen,” for example.

Safari normally updates via system updates, but in this case it was a first-generation iPad Air stuck on iOS 12, and no more updates were possible for what Apple considers a “vintage” device. Same deal with a Chromebook stuck at Chrome 76.

A couple of little optional chaining question mark (?) characters borked the whole dang site. Unfortunate. That “serve two bundles, modern and legacy” idea is still pretty smart.


Speaking of moms, I was reminded of an older episode of ShopTalk we did with Paul Irish’s mom that has a lot of this “regular person using the internet” vibes.

To Shared LinkPermalink on CSS-Tricks


The Optional Chaining Operator, “Modern” Browsers, and My Mom originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/the-optional-chaining-operator-modern-browsers-and-my-mom/feed/ 4 362890
“Evergreen” Does Not Mean Immediately Available https://css-tricks.com/evergreen-does-not-mean-immediately-available/ https://css-tricks.com/evergreen-does-not-mean-immediately-available/#comments Tue, 01 Feb 2022 15:38:41 +0000 https://css-tricks.com/?p=362169 I have a coworker who is smart, capable, and technologically-literate. Like me, they work on the web full-time.

When they are sharing their screen in a meeting, I find myself disassociating fixating on the red update button in their copy …


“Evergreen” Does Not Mean Immediately Available originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I have a coworker who is smart, capable, and technologically-literate. Like me, they work on the web full-time.

When they are sharing their screen in a meeting, I find myself disassociating fixating on the red update button in their copy of Chrome.

An angry red button labeled "Update".

Clicking this button would start the process to update Chrome to the latest stable version.

I’ve asked some probing questions about how frequently they reboot, which would also force Chrome to update upon relaunch. That’s the point of an “evergreen” browser, right? It’s easy to make sure you’re always using the latest and greatest version.

It turns out they prefer to wait until they absolutely have to because of the disruption it would cause in their daily workflow. Their behavior makes sense. They are prioritizing the quality of their overall computing experience, rather than catering to the demands of one specific app.

Like me, my coworker also uses a top-of-the-line laptop to get things done. This means that the laptop can go for months without needing a reboot. Ironically, this might be a situation where a craptop is conditionally forced to have a faster browser upgrade path.

Evergreen browsers

Before the advent of evergreen browsers, you would need to go to the manufacturer’s website and manually download and install the update. Prior to that you had to use a CD or floppy disk.

A floppy disk used for installing Netscape Navigator.
Source: Floppy Disk of Netscape Navigator. Toshihiro Oimatsu, CC BY 2.0, via Wikimedia Commons.

By contrast, an evergreen browser is any browser that can automatically update itself. By this, I mean the browser will automatically pull down the code required to add new features and fix bugs once it has been released by the browser’s manufacturer. The update itself occurs with:

  • a prompt shown to the person using the browser that triggers an application restart,
  • a download that happens in the background and gets applied on application restart, or
  • on device restart.

The browsers themselves

Nearly all major browsers are evergreen. This includes Google Chrome, Microsoft Edge, and Mozilla Firefox.

Apple Safari is quasi-evergreen. By this I mean it automatically receives updates, but awkwardly requires them to be done via the macOS operating system update interface, where other system-wide updates are located.

A sub-window floating over macOS’s Software Update preference pane. It shows options for updating macOS Big Sur to version 11.6.2, Command Line Tools for Xcode to version 13.2, Safari to version 15.2, and Safari Technology Preview to version 137, all of which are considered Evergreen software. Screenshot.

If you haven’t been paying attention, the Safari team has been making a ton of improvements to their browser in the past few months—I’d love to see them continue with this trend by making the browser update path decoupled from existing macOS and iOS upgrade workflows.

The situation

With the actual, final, no-seriously-we-mean-it-this-time death of Internet Explorer, evergreen browsers are now the main consideration for desktop and laptop browsers. This is great! It means we can spend a lot less time fretting about who can use what.

Spending less time does not mean spending no time, however.

Delayed effects

Support from all evergreen browsers on caniuse.com does not necessarily mean support exists on the device a person is using—updates that have been pushed out don’t automatically get instantly applied.

Because of these two factors, I advocate for tempering your excitement with some restraint. It can be very tempting to rush and use the new and the shiny. Believe me, I’m not exempt from this urge—CSS is about to go from great to amazing, and the urge to use new features is very real.

Instead, wait a bit. Work with the platform’s ability to create progressively enhanced experiences with CSS and JavaScript.

Leverage the platform

The web is really good at being resilient, provided you work with its grain.

Both CSS and JavaScript have the ability to conditionally serve up experiences for browsers that support new features while providing alternatives for those that don’t.

Instead of looking at the support table for something on caniuse.com and thinking, “I wish more browsers supported this feature so that I could use it!”, you can instead think “I’m going to use this feature today, but treat it as an experimental feature.”

—Jeremy Keith, “Continuous partial browser support”

JavaScript

You can use JavaScript to query whether or not a browser supports a certain feature. For example, the Navigator interface provides a mechanism for querying a user agent’s capabilities.

if (!(“geolocation” in navigator)) {
  // Logic if a user's current geographic location isn't available
} else {
  // Logic that is based on a user's current geographic location
}

In this example, I am inverting a request for support for a browser’s Geolocation interface. Although its syntax is initially a little confusing to parse, it helps emphasize a progressive enhancement approach.

Assume geolocation functionality isn’t supported to start and provide a way to accommodate the person using this browser (say manually typing in a ZIP Code, etc.). With this use case covered, you can then confidently build an experience for browser that support geolocation.

This thinking also extends to all other browser features and capabilities.

CSS

Like most other programming languages, CSS also lets us use if-like statements.

For example, the @supports at rule allows you to create a conditional statement that targets whether or not a browser supports something, and then apply logic to it. Browsers that honor the feature will utilize those styles, and browsers that don’t will ignore them. It is a concise, clever, adaptable solution.

.component {
  /* Base appearance */
}

@supports (grid-template-columns: subgrid;) {
  .component {
    /* Styling and positioning enhancement tweaks if subgrid is supported */
  }
}

For this example, this progressive enhancement approach ensures that a component’s content and functionality is preserved for every browser, but only creates fancy layouts for browsers capable of supporting them.

When can I remove this stuff?

Yes, this approach adds more code, and more code means more complexity and maintenance. But it’s very important code. You might even call it technical debt and you’d be correct. But technical debt can be a good thing, like an investment in the future.

You may want to remove that complexity when it’s no longer needed. Knowing the right time to do that in this age of evergreen browsers is difficult, but I have a couple of suggestions:

Patience is a virtue

In terms of waiting, I’d advise a conservative 6-ish months from release of a new feature before even beginning to think about investigating if you can remove feature detection. This accounts for:

  • Reboots
  • Update procrastinators
  • Update avoiders
  • Hardware refresh cycles
  • Corporate update policies,
  • etc.

I would also say that rough six month timeframe is in terms of a general, global web audience. This guesstimate changes if you cater to a specialized audience. The way to know who you actually serve? Analytics, yes, but also talking to people.

Maybe don’t

Remember: survivor bias is real. Is the brand new feature you’re using preventing someone from using your website or web app? I say this because some people:

There isn’t a single, specific device, browser, and person we cater to when creating a web experience. Websites and web apps need to adapt to a near-infinite combination of these circumstances to be effective. This adaptability is a large part of what makes the web such a successful medium.

Consider doing the hard work to make it easy and never remove feature queries and @supports statements. This creates a robust approach that can gracefully adapt to the past, as well as the future.

The future is uncertain

We’re long past the age of desktop computers. Browsers are showing up in more and more places: phones, tablets, watches, ebook readers, digital cameras, kiosks, televisions, home assistants, vending machines, photo frames, graphing calculators, ATMs, point of sale terminals, exercise equipment, video game consoles, billboards, refrigerators, virtual reality, and cars.

Who knows what devices browsers will be included with in the future, or what capabilities they’ll have? Future-proof (and, er, past-proof) yourself with an approach that accommodates it.


Thank you to to Jim Nielsen for their feedback.


“Evergreen” Does Not Mean Immediately Available originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/evergreen-does-not-mean-immediately-available/feed/ 10 362169
Be Prepared for Failure and Handle it Gracefully https://css-tricks.com/be-prepared-for-failure-and-handle-it-gracefully/ https://css-tricks.com/be-prepared-for-failure-and-handle-it-gracefully/#comments Mon, 20 Dec 2021 15:22:04 +0000 https://css-tricks.com/?p=358037 When I was working at my first “real” job in the field in the mid-2000s, it was hammered in the web dev field to build tiny websites (no more than 100KB per page), only use JavaScript for special effects, and …


Be Prepared for Failure and Handle it Gracefully originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
When I was working at my first “real” job in the field in the mid-2000s, it was hammered in the web dev field to build tiny websites (no more than 100KB per page), only use JavaScript for special effects, and make sure everything—from images to Flash content—has a fallback so that JavaScript features progressively enhance. If there was no JavaScript, the site still works 100%, only not as fancy.

The reason for this advice was simple: in the early days of the web, everyone was limited to a dial-up Internet connection which was really slow. Shoot, it took a few minutes just to connect to the Internet, let alone access a website. Hit the “Connect” button and go make some coffee or grab a smoke because it is literally going to take a few minutes. So, in that sense, it’s perfectly reasonable that the key principles of building a good website in the mid-2000s involved making the site small with solid fallbacks for content.

I miss those days a lot. In my opinion, those constraints made the web better. But, as time wears on and the service provider technology continues to improve in the U.S. with broadband (and eventually fiber), the constraints are no longer viewed as an issue anymore.

Today, the rules of web development are completely different. It’s more about the developer experience and less about the user experience: build processes, deciding which framework and tech stack is used, and figuring out where the site lands in Google search results. Sadly, gatekeeping (i.e. you’re not a real “x” if you don’t “y”) and framework battles have replaced the “how to make this cool thing without JavaScript” conversations. I really don’t pay attention to this stuff because, at the end of the day, it all renders down to HTML, CSS, and JavaScript in the browser; use whatever works for you.

Cartoon illustration from The Simpsons TV show showing a yellow hand holding a newspaper clipping with the headline Old Woman Yells At Cloud. A photo of the woman shows her with wild gray hair and a raised right fist as she holds two nervous-looking cats.
Source: @aexm

What does bother me, though, is the fact that huge sites that require JavaScript just to use have become the accepted norm. Current stats show websites are weighing in at an average of—big gulp2MB per page?! And, if you do not have JavaScript enabled, be prepared for the blizzard (that’s what I call sites that only display a white screen).

JavaScript has become a third pillar of the web. I know JavaScript is super useful, so why am I picking on it? I’m picking on the fact that a lot of sites can’t even load if the JavaScript is not there. Since when did loading HTML and CSS rely on JavaScript?

I recently moved to a rural area on the outskirts of a major city, and I’m reminded of those early web dev days because, once again, I have a horrible Internet connection on my mobile device. The broadband connection is okay, but when I’m outside or the power goes out, I’m left with the same old slow experience of the dial-up days. When I’m browsing the web on my mobile device, I live for Reader Mode and I have to turn off things like JavaScript (and most images, compliments of JavaScript lazy loading) because it is too much to download and run. But, just by switching off JavaScript, a lot of sites won’t even load. The white screen of death is not the result of my phone dying; it’s my access to the Internet. And this white screen appears on site after site, hence the blizzard.

Six screenshots of webpages on a mobile device in a three-by-two grid. All of the screens either show blank content or a notice asking users to enable JavaScript to view the website.
My all-too-familiar experience browsing the web on a mobile device.

For the most part, I can get by using my WiFi in the house to do necessary Internet things, like working, shopping, and paying bills. But, late this summer, my electricity went out. So, I went to the electric company’s website on my mobile device to see when service might be back on because the entire house runs on electricity and I need to know if we need to make arrangements for things, like food and water. But, the electric company’s mobile website is large (3MB transferred and 8.6MB in resources — ouch) and won’t load (even with JavaScript enabled).

Angry, I went to Twitter and posted my outrage.

https://twitter.com/_hmig/status/1432093685924179968

I got some pretty awesome responses showing me that some sites actually do handle constraints really well, like traintimes.org.uk and the text-only version of NPR. YES! Why don’t they provide a text-only version of the outage page so when people actually need that page of the site, they can use it even when the conditions are the worst?

My power-outage scenario is a one-off type of situation, but there are people across the U.S. and the entire world who live with unreliable Internet because there is no other option available to them. NPR published an article covering the struggles of trying to teach students during the pandemic in areas where communities have unreliable Internet and it’s hard to believe that this is the current state of Internet availability in any part of the U.S. But, the fact remains that it is.

It’s clear a lot of people would benefit from the constraints of the early days of web development. The beauty of websites built before broadband networks were available (like the infamous Space Jam site from 1996) is that they can load on any kind of device from anywhere under almost any circumstances because they were built with these constraints in mind. Hopefully, all developers start employing an adaptive loading strategy that provide solutions for users on slow networks or low-end devices.


Be Prepared for Failure and Handle it Gracefully originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/be-prepared-for-failure-and-handle-it-gracefully/feed/ 4 358037
Embrace the Platform https://css-tricks.com/embrace-the-platform/ https://css-tricks.com/embrace-the-platform/#comments Mon, 13 Dec 2021 15:18:08 +0000 https://css-tricks.com/?p=358612 So what is the one thing that people can do is to make their website better? To answer that, let’s take a step back in time …

The year is 1998, and the web is on the rise. In an …


Embrace the Platform originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
So what is the one thing that people can do is to make their website better? To answer that, let’s take a step back in time …

The year is 1998, and the web is on the rise. In an attempt to give a high-level overview of the architecture of the WWW, Tim Berners-Lee — yes, that Tim Berners-Lee — publishes a paper called “Web Architecture from 50,000 feet”. The report covers lots of things: content negotiation, the semantic web, HTML, CSS, and cool URIs (which don’t change), among others.

In the article, Berners-Lee also notes a few design principles very early on. One of those principles is the “Rule of Least Power.”

The Rule of Least Power goes like this:

When designing computer systems, one is often faced with a choice between using a more or less powerful language for publishing information, for expressing constraints, or for solving some problem. […] The “Rule of Least Power” suggests choosing the least powerful language suitable for a given purpose.

We have three main languages available on the web for the front end:

HTML

Semantically describes the content

CSS

Controls the presentation and layout

JavaScript

Adds interactivity and behavior

The Rule of Least Power suggests trying and doing as much as possible using HTML before resorting to CSS. Once CSS is no longer sufficient, grab for JavaScript — but only if you really have to.

As Derek Featherstone nicely put it:

In the web front-end stack — HTML, CSS, JS, and ARIA — if you can solve a problem with a simpler solution lower in the stack, you should. It’s less fragile, more foolproof, and just works.

💡 Hold up: This doesn’t mean you should set font sizes and colors via markup — a thing we used to do in the “olden” days of the web. Case in point: one of the rules covered in Berners-Lee’s piece is the separation of form and content.

The broken web

It has been almost 25 years since Berners-Lee published that article. Yet, somehow, the message he sent didn’t get through, and many developers — but not all — are unaware of it. Take this situation Drew Devault encountered not so long ago:

My browser has been perfectly competent at submitting HTML forms for the past 28 years, but for some stupid reason some developer decided to reimplement the form in JavaScript, and now I can’t pay my electricity bill without opening up the dev tools.

Sadly, this is no isolated case but rather a familiar phenomenon. All too often, I see websites and libraries that try to be clever or attempt to reinvent the wheel — primarily by throwing a bunch of JavaScript at it. In their attempt to do so, they achieve the exact opposite of what they were aiming at: those websites become less functional, less accessible, or — even worse — don’t work at all under certain conditions.

While a form might be a familiar example, there are more situations where applying the Rule of Least Power gives better results:

In all these examples, we can move some functionality from an upper into a lower layer. Berners-Lee would be proud of us.

Resilience

By choosing a technology lower in the web stack, closer to the core of the web platform, we also gain the benefit of built-in resilience against failures.

JavaScript is terrible at failing. One script that fails to load or gets mangled in the process, or one wrong argument to a function, and your whole app may no longer work. If an error message like “Cannot read property x of undefined” sounds familiar to you, you know what I’m talking about.

CSS, on the other hand, is very good at failing. Even if you have a syntax error in one of your style sheets, the rest of your CSS will still work. Same with HTML. These are forgiving languages.

If you doubt why you should use the Rule of Least Power, Jeremy Keith brings us the answer in his article “Evaluating Technology”:

We tend to ask “How well does it work?”, but what you should really be asking is “How well does it fail?”

Jeremy Keith

We can do better

A high-profile website that could benefit from the Rule of Least Power is the Nike website. When you visit their site with JavaScript disabled, you don’t get to see any images, nor do you get to order any shoes.

The same Nike product page with JavaScript (left) and without (right).

This over-reliance on JavaScript is not necessary as all those broken features can be built with technologies lower in the frontend stack:

  • Why not immediately include <img> elements to show the shoe photos instead of dynamically injecting them via JavaScript?
  • Why not use a <form> with a <select> (to choose your size) and a submit <button> to order the shoes, rather than managing the entire state and submission through JavaScript?

☝️ If you’re wondering why someone would ever browse the web with JavaScript disabled: it’s often not their choice but an external factor that’s interfering. See “Everyone has JavaScript, right?” for a good explanation on the topic.

Even worse offenders in this category are high-profile sites like Instagram and Twitter. Without JavaScript these websites don’t work at all. They either give you a warning, or simply remain blank. Since when is JavaScript required to show text and images on the web?

Progressive enhancement

It doesn’t have to be as bad as this isolated Nike example. Sometimes it’s smaller components that refuse to work when JavaScript fails. Take a tabbed interface as an example. Even though you can find many JavaScript libraries that provide this functionality, the kicker is that you don’t need JavaScript for that, as HTML, CSS, and ARIA themselves are already capable of getting you very far.

Once you have those base layers in place, use JavaScript to improve the experience further. Think of JavaScript as an enhancement instead of a requirement.

The same goes for certain CSS features that may or may not be available. Provide basic styling, and when a feature is available — detectable using @supports — enhance the result you already had.

This approach is known as progressive enhancement: You add functionality as more features become available, making the result better as far as the experience goes, but not so much that the feature cannot work without those extra flourishes.

And for browsers that don’t yet support a particular new feature, you can try and find a polyfill that provides that functionality to the browsers.

The web catches up

Since the web’s early days, we’ve seen the web platform gain many new features over time. New HTML elements were defined, JavaScript (the language) has matured, and CSS has gotten many powerful new features for building layouts, animating elements, etc.

Things that were impossible years ago and that could only be done by relying on external technologies, like Flash, are now built into the browser itself.

A classic example is the features jQuery introduced. Over ten years ago, jQuery was the very first thing to drop into a project. Today, that’s no longer the case, as the web platform has caught up and now has document.querySelectorAll(), Element.classList, etc. built-in — all features inspired upon features jQuery gave us. You could say that jQuery was a polyfill before polyfills even were called polyfills.

While jQuery might be a familiar example, there are many other situations where the web has caught up:

The central theme here is that these features no longer rely on a polyfill or an external library but have shifted closer to the core of the web stack. The web catches up.

While some of these new APIs can be pretty abstract, there are libraries out there that you can plug into your code. An example is Redaxios, which uses fetch under the hood while also exposing a developer-friendly, Axios-compatible API. It wouldn’t surprise me if these convenience methods eventually trickle down into the web platform.

In closing

What Berners-Lee wrote almost 25 years ago stands the test of time. It’s up to us, developers, to honor that message. By embracing what the web platform gives us — instead of trying to fight against it — we can build better websites.

Keep it simple. Apply the Rule of Least Power. Build with progressive enhancement in mind.

HTML, CSS, and JavaScript — in that order.


Embrace the Platform originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/embrace-the-platform/feed/ 13 358612
Apollo GraphQL without JavaScript https://css-tricks.com/apollo-graphql-without-javascript/ Wed, 29 Jan 2020 15:22:09 +0000 https://css-tricks.com/?p=302550 It’s cool to see progressive enhancement being done even while using the fanciest of the fancy front-end technologies.

This is a button in a JSX React component that has a click handler applied directly to it that fires a data …


Apollo GraphQL without JavaScript originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
It’s cool to see progressive enhancement being done even while using the fanciest of the fancy front-end technologies.

This is a button in a JSX React component that has a click handler applied directly to it that fires a data mutation Ajax request through Apollo GraphQL. That is about the least friendly environment for progressive enhancement I can imagine.

Kitty Giraudel writes that they do server-side rendering already, so the next tricky part is the click handler. Without JavaScript, the only mechanism we have for posting data is a <form>, so that’s what they do. It submits to the /graphql endpoint with the data it needs to perform the mutation via hidden inputs, plus additional data on where to redirect upon success or failure.

Pretty neat.

To Shared LinkPermalink on CSS-Tricks


Apollo GraphQL without JavaScript originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
302550
Using <details> for Menus and Dialogs is an Interesting Idea https://css-tricks.com/using-details-for-menus-and-dialogs-is-an-interesting-idea/ https://css-tricks.com/using-details-for-menus-and-dialogs-is-an-interesting-idea/#comments Thu, 21 Mar 2019 21:06:28 +0000 http://css-tricks.com/?p=283765 Using <details for a menu may be an interesting idea, but perhaps not something to actually ship in production. See “More Details on <details for an explanation.

One of the most empowering things you can learn as a new …


Using <details> for Menus and Dialogs is an Interesting Idea originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Using <details> for a menu may be an interesting idea, but perhaps not something to actually ship in production. See “More Details on <details> for an explanation.

One of the most empowering things you can learn as a new front-end developer who is starting to learn JavaScript is to change classes. If you can change classes, you can use your CSS skills to control a lot on a page. Toggle a class to one thing, style it this way, toggle to another class (or remove it) and style it another way.

But there is an HTML element that also does toggles! <details>!

For example, it’s definitely the quickest way to build an accordion UI.

Extending that toggle-based thinking, what is a user menu if not for a single accordion? Same with modals. If we went that route, we could make JavaScript optional on those dynamic things. That’s exactly what GitHub did with their menu.

Inside the <details> element, GitHub uses some Web Components (that do require JavaScript) to do some bonus stuff, but they aren’t required for basic menu functionality. That means the menu is resilient and instantly interactive when the page is rendered. 

Mu-An Chiou, a web systems engineer at GitHub who spearheaded this, has a presentation all about this!

The worst strike on <details> is its browser support in Edge, but I guess we won’t have to worry about that soon, as Edge will be using Chromium… soon? Does anyone know?

Using <details> for Menus and Dialogs is an Interesting Idea originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/using-details-for-menus-and-dialogs-is-an-interesting-idea/feed/ 6 283765
How @supports Works https://css-tricks.com/how-supports-works/ https://css-tricks.com/how-supports-works/#comments Mon, 18 Feb 2019 17:39:13 +0000 http://css-tricks.com/?p=281839 CSS has a neat feature that allows us to test if the browser supports a particular property or property:value combination before applying a block of styles — like how a @media query matches when, say, the width of the browser …


How @supports Works originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
CSS has a neat feature that allows us to test if the browser supports a particular property or property:value combination before applying a block of styles — like how a @media query matches when, say, the width of the browser window is narrower than some specified size and then the CSS within it takes effect. In the same spirit, the CSS inside this feature will take effect when the property:value pair being tested is supported in the current browser. That feature is called @supports and it looks like this:

@supports (display: grid) {
  .main {
    display: grid;
  }
}

Why? Well, that’s a bit tricky. Personally, I find don’t need it all that regularly. CSS has natural fallback mechanisms such that if the browser doesn’t understand a property:value combination, then it will ignore it and use something declared before it if there is anything, thanks to the cascade. Sometimes that can be used to deal with fallbacks and the end result is a bit less verbose. I’m certainly not a it’s-gotta-be-the-same-in-every-browser kinda guy, but I’m also not a write-elaborate-fallbacks-to-get-close kinda guy either. I generally prefer a situation where a natural failure of a property:value doesn’t do anything drastic to destroy functionality.

That said, @supports certainly has use cases! And as I found out while putting this post together, plenty of people use it for plenty of interesting situations.

A classic use case

The example I used in the intro is a classic one that you’ll see used in lots of writing about this topic. Here it is a bit more fleshed out:

/* We're gonna write a fallback up here in a minute */

@supports (display: grid) {
  .photo-layout {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    grid-gap: 2rem;
  }
}

Nice grid! Repeating and auto-filling columns is a sweet feature of CSS grid. But, of course, there are browsers that don’t support grid, or don’t support all the specific features of it that I’m using above there.

For example, iOS shipped support for CSS grid in version 10.2, but iOS has had flexbox support since version 7. That’s a decent gap of people with older iOS devices that do support flexbox but not grid. I’m sure there are more example gaps like that, but you probably get the idea.

It may be acceptable to let the fallback for this be nothing, depending on the requirements. For example, vertically stacked block-level elements rather than a multi-column grid layout. That’s often fine with me. But let’s say it’s not fine, like a photo gallery or something that absolutely needs to have some basic grid-like structure. In that case, starting with flexbox as the default and using @supports to apply grid features where they’re supported may work better…

.photo-layout {
  display: flex;
  flex-wrap: wrap;
  > div {
    flex: 200px;
    margin: 1rem;
  }
}

@supports (display: grid) {
  .photo-layout {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    grid-gap: 2rem;
    > div {
      margin: 0;
    }
  }
}

The “fallback” is the code outside of the @supports block (the properties above the block in the example above), and the grid code is either inside or after. The @supports block doesn’t change any specificity, so we need the source order to help make sure the overrides work.

Notice I had to reset the margin on the divs inside the @supports block. That’s the kind of thing I find a bit annoying. There is just enough crossover between the two scenarios that it requires being super aware of how they impact each other.

Doesn’t that make you wish it could be entirely logically separated…

There is “not” logic in @supports blocks, but that doesn’t mean it should always be used

Jen Simmons put this example in an article called Using Feature Queries in CSS a few years ago:

/* Considered a BAD PRACTICE, at least if you're supporting IE 11 and iOS 8 and older */
@supports not (display: grid) {
   /* Isolated code for non-support of grid */
}
@supports (display: grid) {
   /* Isolated code for support of grid */
}

Notice the not operator in the first block. That’s checking for browsers that do not support grid in order to apply certain styles to those browsers. The reason this approach is considered bad practice is that the browser support for @supports itself has to be considered!. That’s what makes this so dang tricky.

It’s very appealing to write code in logically separated @supports blocks like that because then it’s starting from scratch each time and doesn’t need to be overriding previous values and dealing with those logical mind-benders. But let’s go back to the same iOS situation we considered before… @supports shipped in iOS in version 9 (right between when flexbox shipped in iOS 7 and grid shipped in iOS 10.2). That means any flexbox fallback code in a @supports block using the not operator to check for (display: grid) {} support wouldn’t work in either iOS 7 or 8, meaning the fallback now needs a fallback from working in browsers it otherwise would have. Phew!

The big reason to reach for @supports is to account for very different implementations of something depending on feature support where it becomes easier to reason and distinguish between those implementations if the blocks of code are separated.

We’ll probably get to a point where we can use mutually-exclusive blocks like that without worry. Speaking of which…

@supports is likely to be more useful over time.

Once @supports is supported in all browsers you need to support, then it’s good to start using it more aggressively and without having to factor in the complication of considering whether @supports itself is supported. Here’s the support grid on that:

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

ChromeFirefoxIEEdgeSafari
2822No129

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
1271274.49.0-9.2

Basically, IE 11 and any iOS device stuck on iOS 8 are the pain points. If your requirements are already past those, then you’re good to use @supports more freely.

The irony is that there hasn’t been a ton of CSS features shipping that have big clear @supports use cases — but there are some! Apparently, it’s possible to test new fancy stuff like Houdini:

(I’m not sure entirely what you’d put in the @supports block to do that. Has anyone else done this?)

When @supports isn’t doing anything useful

I’ve seen a good amount of @supports uses in the wild where the end result is exactly as it would be without using it. For example…

@supports (transform: rotate(5deg)) {
  .avatar {
    transform: rotate(5deg);
  }
}

On some level, that makes perfect logical sense. If transforms are supported, use them. But it’s unnecessary if nothing different is happening in a non-support scenario. In this case, the transform can fail without the @supports block and the result is the same.

Here’s another example of that shaking out.

There are browser extensions for playing with @supports

There are two of them!

They are both centered around the idea that we can write @supports blocks in CSS and then toggle them on and off as if we’re looking at a rendering of the code in a browser that does or doesn’t support that feature.

Here’s a video of Keith’s tool applied to the scenario using grid with a flexbox fallback:

This is fun to play with and is very neat tech. But in this exact scenario, if I was able to pull off the layout identically with flexbox, then I’d probably just do that and save that little bit of technical debt.

Ire’s tool, which she wrote about in the article Creating The Feature Queries Manager DevTools Extension, has a slightly different approach in that it shows the feature queries that you actually wrote in your CSS and provides toggles to flip them on and off. I don’t think it works through iframes though, so I popped open Debug Mode to use it on CodePen.

More real world use cases for @supports

Here’s one from Erik Vorhes. He’s styling some custom checkboxes and radio buttons here, but wraps all of it in a @supports block. None of the styling gets applied unless the block passes the support check.

@supports (transform: rotate(1turn)) and (opacity: 0) {
  /* all the styling for Erik's custom checkboxes and radio buttons */
}

Here are several more I’ve come across:

  • Joe Wright and Tiago Nunes mentioned using it for position: sticky;. I’d love to see a demo here! As in, where you go for position: sticky;, but then have to do something different besides let it fail for a non-supporting browser.
  • Keith Grant and Matthias Ott mention using it for object-fit: contain;. Matthias has a demo where positioning trickery is used to make an image sort of fill a container, which is then made easier and better through that property when it’s available.
  • Ryan Filler mentions using it for mix-blend-mode. His example sets more opacity on an element, but if mix-blend-mode is supported, it uses a bit less and that property which can have the effect of seeing through an element on its own.
  • .thing {
      opacity: 0.5;
    }
    @supports (mix-blend-mode: multiply) {
      .thing {
        mix-blend-mode: multiply;
        opacity: 0.75;
      }
    }
  • Rik Schennink mentioned the backdrop-filter property. He says, “when it’s supported the opacity of the background color often needs some fine tuning.”
  • Nour Saud mentioned it can be used to detect Edge through a specific vendor-prefixed property: @supports (-ms-ime-align:auto) { }.
  • Amber Weinberg mentioned using it for clip-path because adjusting the sizing or padding of an element will accommodate when clipping is unavailable.
  • Ralph Holzmann mentioned using it to test for that “notch” stuff (environment variables).
  • Stacy Kvernmo mentioned using it for the variety of properties needed for drop capping characters. Jen Simmons mentions this use case in her article as well. There is an initial-letter CSS property that’s pretty fantastic for drop caps, but is used in conjunction with other properties that you may not want to apply at all if initial-letter isn’t supported (or if there’s a totally different fallback scenario).

Here’s a bonus one from Nick Colley that’s not @supports, but @media instead! The spirit is the same. It can prevent that “stuck” hover state on touch devices like this:

@media (hover: hover) {
  a:hover {
    background: yellow;
  }
}

Logic in @supports

Basic:

@supports (initial-letter: 4) {

}

Not:

@supports not (initial-letter: 4) {

}

And:

@supports (initial-letter: 4) and (transform: scale(2)) {

}

Or:

@supports (initial-letter: 4) or (-webkit-initial-letter: 4) {

}

Combos:

@supports ((display: -webkit-flex) or
          (display: -moz-flex) or
          (display: flex)) and (-webkit-appearance: caret) {

}

JavaScript Variant

JavaScript has an API for this. To test if it exists…

if (window.CSS && window.CSS.supports) {
  // Apparently old Opera had a weird implementation, so you could also do:
  // !!((window.CSS && window.CSS.supports) || window.supportsCSS || false)
}

To use it, either pass the property to it in one param and the value in another:

const supportsGrid = CSS.supports("display", "grid");

…or give it all in one string mirroring the CSS syntax:

const supportsGrid = CSS.supports("(display: grid)");

Selector testing

At the time of this writing, only Firefox supports this sort of testing (behind an experimental flag), but there is a way to test the support of selectors with @supports. MDN’s demo:

@supports selector(A > B) {
}

You?

Of course, we’d love to see Pens of @supports use cases in the comments. So share ’em!


How @supports Works originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/how-supports-works/feed/ 14 281839
New CodePen Feature: Prefill Embeds https://css-tricks.com/new-codepen-feature-prefill-embeds/ https://css-tricks.com/new-codepen-feature-prefill-embeds/#comments Mon, 21 Jan 2019 16:11:53 +0000 http://css-tricks.com/?p=281507 I’m very excited this feature is ready for y’all. You can take any <pre> block of HTML, CSS, and JavaScript (or any combination of them) and enhance it into an embed, meaning you can see the rendered output. Very progressive …


New CodePen Feature: Prefill Embeds originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I’m very excited this feature is ready for y’all. You can take any <pre> block of HTML, CSS, and JavaScript (or any combination of them) and enhance it into an embed, meaning you can see the rendered output. Very progressive enhancement friendly! It also lets you pass in stuff like external resources, making it a great choice for, say, documentation sites or the like.

Here’s an example right here:

<div id="root"></div>
@import url("https://fonts.googleapis.com/css?family=Montserrat:400,400i,700");
body {
  margin: 0;
  font-family: Montserrat, sans-serif;
}
header {
  background: #7B1FA2;
  color: white;
  padding: 2rem;
  font-weight: bold;
  font-size: 125%
}
class NavBar extends React.Component {
  render() {
    return(
      <header>
        Hello World, {this.props.name}!
      </header>
    );
  }
}
ReactDOM.render(
  <NavBar name="Chris" />,
  document.getElementById('root')
);

What you can’t see is is this block, appended to the embed snippet:

<pre data-lang="html"><div id="root"></div></pre>
<pre data-lang="scss" >@import url("https://fonts.googleapis.com/css?family=Montserrat:400,400i,700");
body {
  margin: 0;
  font-family: Montserrat, sans-serif;
}
header {
  background: #7B1FA2;
  color: white;
  padding: 2rem;
  font-weight: bold;
  font-size: 125%
}</pre>
  <pre data-lang="babel">class NavBar extends React.Component {
  render() {
    return(
      <header>
        Hello World, {this.props.name}!
      </header>
    );
  }
}
ReactDOM.render(
  <NavBar name="Chris" />,
  document.getElementById('root')
);</pre>

If I want to update that demo, I can do it by editing this blog post. No need to head back to CodePen. 🙌

To Shared LinkPermalink on CSS-Tricks


New CodePen Feature: Prefill Embeds originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/new-codepen-feature-prefill-embeds/feed/ 2 281507
Nobody is quite wrong. https://css-tricks.com/nobody-is-quite-wrong/ https://css-tricks.com/nobody-is-quite-wrong/#comments Mon, 17 Dec 2018 21:57:26 +0000 http://css-tricks.com/?p=274940 There are two opposing views on using non-polyfillable new web features that I find are both equally common in our industry:

  1. Websites don’t need to look the same in every browser. The concept of progressive enhancement helps with that. There


Nobody is quite wrong. originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
There are two opposing views on using non-polyfillable new web features that I find are both equally common in our industry:

  1. Websites don’t need to look the same in every browser. The concept of progressive enhancement helps with that. There are tools, even native language features, that help with this.
  2. If browser support isn’t where I want it to be, it’s just exotic eye candy for demos and not to be used.

I’m not sure I’d say either one of these is more or less correct than the other.

I also imagine it doesn’t come as much of surprise that I support the thinking behind #1. It’s perfectly possible to design and implement things that behave differently in different browsers and conditions. That’s essentially what responsive design is, and that’s pretty much the entire internet now.

The backbone of progressive enhancement is starting with a working foundation that works everywhere and layering design and functionality on top of that, when possible. There are even native language features to support the idea. @supports rules allow us to write CSS that can do something if a feature is supported and do something else if it isn’t.

This is the entire use case for Modernizr and it has 22,804 stars.

I don’t want to argue against progressive enhancement. Remember, I just said I support that thinking. But I do have some empathy for people and teams that choose not to go there, and end up developing more of a #2 attitude.

It is a bit more work to develop and design features that work in different ways. It might be work that is absolutely worth doing. Or it might not. Either way, it does complicate things. It’s more code, it requires more attention and testing, and it’s a bit harder to reason. It’s technical debt.

Let me be preemptively defensive again: technical debt can be fine, and even intentional. We all incur it in everything we build. My point is that it is helpful to be smart about it and take on an amount of technical debt that is feasible for you to look after in perpetuity.

You might argue that building on a progressive enhancement foundation is, in a sense, less technical debt because you’re building on such a sturdy foundation that less testing and constant tending to is required. Perhaps!

I do get behaving like a #2. It feels safer. It feels like you’re being cautious and responsible. “Hey that’s neat,” you think. “I’ll revisit it in a few years to see if I can use it for real.” I might argue that 1) that’s no fun and 2) almost counter-intuitively, it means you aren’t willing to take a progressive enhancement approach which may make your code ultimately more frail.

It depends, I suppose. It depends on what exactly you’re trying to do. It depends on the weight of that techinical debt. It depends on the team and the rate of developer churn. It depends on documentation. It depends on testing and QA.

You do you.


Nobody is quite wrong. originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/nobody-is-quite-wrong/feed/ 4 274940
New mobile Chrome feature would disable scripts on slow connections https://css-tricks.com/new-mobile-chrome-feature-would-disable-scripts-on-slow-connections/ https://css-tricks.com/new-mobile-chrome-feature-would-disable-scripts-on-slow-connections/#comments Fri, 31 Aug 2018 23:10:34 +0000 http://css-tricks.com/?p=275719 This is a possible upcoming feature for mobile Chrome:

If a Data Saver user is on a 2G-speed or slower network according to the NetInfo API, Chrome disables scripts and sends an intervention header on every resource request. Users are


New mobile Chrome feature would disable scripts on slow connections originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
This is a possible upcoming feature for mobile Chrome:

If a Data Saver user is on a 2G-speed or slower network according to the NetInfo API, Chrome disables scripts and sends an intervention header on every resource request. Users are shown a UI at the bottom of the screen indicating the page has been modified to save data. Users can enable scripts on the page by tapping “Show original” in the UI.

And the people shout: progressive enhancement!

Jeremy Keith:

An excellent idea for people in low-bandwidth situations: automatically disable JavaScript. As long as the site is built with progressive enhancement, there’s no problem (and if not, the user is presented with the choice to enable scripts).

Power to the people!

George Burduli reports:

This is huge news for developing countries where mobile data packets may cost a lot and are not be affordable to all. Enabling NoScript by default will make sure that users don’t burn through their data without knowledge. The feature will probably be available in the Chrome 69, which will also come with the new Material Design refresh.


New mobile Chrome feature would disable scripts on slow connections originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/new-mobile-chrome-feature-would-disable-scripts-on-slow-connections/feed/ 5 275719
A practical guide to Progressive Web Apps for organisations who don’t know anything about Progressive Web Apps https://css-tricks.com/practical-guide-progressive-web-apps-organisations-dont-know-anything-progressive-web-apps/ Mon, 30 Jan 2017 13:58:20 +0000 http://css-tricks.com/?p=250705 Sally Jenkinson:

Progressive Web Apps (sometimes referred to as PWAs, because everything in tech needs an acronym) is the encapsulating term for websites following a certain approach, that meet particular technical criteria. The “app” involvement in the name isn’t an


A practical guide to Progressive Web Apps for organisations who don’t know anything about Progressive Web Apps originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Sally Jenkinson:

Progressive Web Apps (sometimes referred to as PWAs, because everything in tech needs an acronym) is the encapsulating term for websites following a certain approach, that meet particular technical criteria. The “app” involvement in the name isn’t an accident – these creations share much of the functionality that you’ll find in native experiences – but really, they’re just websites.

It’s like if you build a website that is so damn good, you get to have a home screen icon on mobile devices. And good is defined by performance and progressive enhancement.

When you hear people say “I want the web to win” they typically mean “I don’t want to lose the web to proprietary app development”. PWAs seem like an early step toward making web apps not second-class citizens on mobile devices. Maybe there is a future where native app development is web development.

To Shared LinkPermalink on CSS-Tricks


A practical guide to Progressive Web Apps for organisations who don’t know anything about Progressive Web Apps originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
250705
Enhancing a Comment Form https://css-tricks.com/enhancing-comment-form/ Wed, 23 Nov 2016 16:49:43 +0000 http://css-tricks.com/?p=248170 Nice tutorial from Michael Scharnagl in which he takes a perfectly-functional comment form and progressively enhances it with very nice features. Things like custom error messaging, auto-expanding height, and even really fancy stuff like ajax and offline submission.

To Shared


Enhancing a Comment Form originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Nice tutorial from Michael Scharnagl in which he takes a perfectly-functional comment form and progressively enhances it with very nice features. Things like custom error messaging, auto-expanding height, and even really fancy stuff like ajax and offline submission.

To Shared LinkPermalink on CSS-Tricks


Enhancing a Comment Form originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
248170
Progressive Enhancement “Debate” https://css-tricks.com/progressive-enhancement-debate/ Fri, 14 Oct 2016 14:06:22 +0000 http://css-tricks.com/?p=246516 Nolan Lawson:

I had a slide in my talk that read:

In 2016, it’s okay to build a website that doesn’t work without JavaScript.

The condemnation was as swift as it was vocal.

Response by Jeremy Keith:

That framing makes


Progressive Enhancement “Debate” originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Nolan Lawson:

I had a slide in my talk that read:

In 2016, it’s okay to build a website that doesn’t work without JavaScript.

The condemnation was as swift as it was vocal.

Response by Jeremy Keith:

That framing makes it sound like it’s a binary choice: either the website works or it doesn’t. That’s not what I’m suggesting. I’m advocating that the core functionality of a website (which can be as simple as reading some text) should be available without JavaScript (because, let’s face it, that core functionality doesn’t require JavaScript).

Sounds like most people, Nolan and Jeremey included, agree that progressive enhancement doesn’t have to put a website in position of “JavaScript is enabled and loaded, so it works, or not enabled or not loaded, so it doesn’t work.” It’s more nuanced and happens on a feature-by-feature basis of a site.

That’s easy middle ground to agree on. But I think this debate keeps popping up over and over is because “binary” is increasingly how things are done. Not fully loaded JavaScript = white page of nothing. Plenty of folks saying: yikes. Plenty of folks saying: meh.

To Shared LinkPermalink on CSS-Tricks


Progressive Enhancement “Debate” originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
246516
A Redesign with CSS Shapes https://css-tricks.com/redesign-css-shapes/ Tue, 04 Oct 2016 18:51:54 +0000 http://css-tricks.com/?p=246150 CSS Shapes is like the perfect example for progressive enhancement these days. Kinda like border-radius was. Older browsers have square corners! Who cares! CSS Shapes allow you to wrap text irregularly – like along the path of a circle().…


A Redesign with CSS Shapes originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
CSS Shapes is like the perfect example for progressive enhancement these days. Kinda like border-radius was. Older browsers have square corners! Who cares! CSS Shapes allow you to wrap text irregularly – like along the path of a circle().

Eric Meyer uses it on a production page and shows how it works. Here’s some code I snagged from the CSS on the site itself showing it only being applied on large screens with support:

@media only screen and (min-width: 720px) {

  @supports (shape-outside: circle()) {

    .single-page article .complex-content img.right {
      shape-outside: circle(150px at 170px 130px);
    }

  }

}

Jen Simmons has a bunch of great demos of this as well. Looks like shape-outside supports circle(), ellipse(), polygon(), and url() but not path() yet.

To Shared LinkPermalink on CSS-Tricks


A Redesign with CSS Shapes originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
246150