Pankaj Parashar – CSS-Tricks https://css-tricks.com Tips, Tricks, and Techniques on using Cascading Style Sheets. Wed, 22 Mar 2023 20:24:49 +0000 en-US hourly 1 https://wordpress.org/?v=6.2.2 https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&ssl=1 Pankaj Parashar – CSS-Tricks https://css-tricks.com 32 32 45537868 Some Cross-Browser DevTools Features You Might Not Know https://css-tricks.com/some-cross-browser-devtools-features-you-might-not-know/ https://css-tricks.com/some-cross-browser-devtools-features-you-might-not-know/#respond Wed, 22 Mar 2023 20:22:42 +0000 https://css-tricks.com/?p=377264 I spend a lot of time in DevTools, and I’m sure you do too. Sometimes I even bounce between them, especially when I’m debugging cross-browser issues. DevTools is a lot like browsers themselves — not all of the features in …


Some Cross-Browser DevTools Features You Might Not Know originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I spend a lot of time in DevTools, and I’m sure you do too. Sometimes I even bounce between them, especially when I’m debugging cross-browser issues. DevTools is a lot like browsers themselves — not all of the features in one browser’s DevTools will be the same or supported in another browser’s DevTools.

But there are quite a few DevTools features that are interoperable, even some lesser-known ones that I’m about to share with you.

For the sake of brevity, I use “Chromium” to refer to all Chromium-based browsers, like Chrome, Edge, and Opera, in the article. Many of the DevTools in them offer the exact same features and capabilities as one another, so this is just my shorthand for referring to all of them at once.

Search nodes in the DOM tree

Sometimes the DOM tree is full of nodes nested in nodes that are nested in other nodes, and so on. That makes it pretty tough to find the exact one you’re looking for, but you can quickly search the DOM tree using Cmd + F (macOS) or Ctrl + F (Windows).

Additionally, you can also search using a valid CSS selector, like .red, or using an XPath, like //div/h1.

DevTools screenshots of all three browsers.
Searching text in Chrome DevTools (left), selectors in Firefox DevTools (center), and XPath in Safari DevTools (right)

In Chromium browsers, the focus automatically jumps to the node that matches the search criteria as you type, which could be annoying if you are working with longer search queries or a large DOM tree. Fortunately, you can disable this behavior by heading to Settings (F1) → PreferencesGlobalSearch as you typeDisable.

After you have located the node in the DOM tree, you can scroll the page to bring the node within the viewport by right-clicking on the nod, and selecting “Scroll into view”.

Showing a highlighted node on a webpage with a contextual menu open to scroll into view

Access nodes from the console

DevTools provides many different ways to access a DOM node directly from the console.

For example, you can use $0 to access the currently selected node in the DOM tree. Chromium browsers take this one step further by allowing you to access nodes selected in the reverse chronological order of historic selection using, $1, $2, $3, etc.

Currently selected node accessed from the Console in Edge DevTools

Another thing that Chromium browsers allow you to do is copy the node path as a JavaScript expression in the form of document.querySelector by right-clicking on the node, and selecting CopyCopy JS path, which can then be used to access the node in the console.

Here’s another way to access a DOM node directly from the console: as a temporary variable. This option is available by right-clicking on the node and selecting an option. That option is labeled differently in each browser’s DevTools:

  • Chromium: Right click → “Store as global variable”
  • Firefox: Right click → “Use in Console”
  • Safari: Right click → “Log Element”
Screenshot of DevTools contextual menus in all three browsers.
Access a node as a temporary variable in the console, as shown in Chrome (left), Firefox (center), and Safari (right)

Visualize elements with badges

DevTools can help visualize elements that match certain properties by displaying a badge next to the node. Badges are clickable, and different browsers offer a variety of different badges.

In Safari, there is a badge button in the Elements panel toolbar which can be used to toggle the visibility of specific badges. For example, if a node has a display: grid or display: inline-grid CSS declaration applied to it, a grid badge is displayed next to it. Clicking on the badge will highlight grid areas, track sizes, line numbers, and more, on the page.

A grid overlay visualized on top of a three-by-three grid.
Grid overlay with badges in Safari DevTools

The badges that are currently supported in Firefox’s DevTools are listed in the Firefox source docs. For example, a scroll badge indicates a scrollable element. Clicking on the badge highlights the element causing the overflow with an overflow badge next to it.

Overflow badge in Firefox DevTools located in the HTML panel

In Chromium browsers, you can right-click on any node and select “Badge settings…” to open a container that lists all of the available badges. For example, elements with scroll-snap-type will have a scroll-snap badge next to it, which on click, will toggle the scroll-snap overlay on that element.

Taking screenshots

We’ve been able to take screenshots from some DevTools for a while now, but it’s now available in all of them and includes new ways to take full-page shots.

The process starts by right-clicking on the DOM node you want to capture. Then select the option to capture the node, which is labeled differently depending on which DevTools you’re using.

Screenshot of DevTools in all three browsers.
Chrome (left), Safari (middle), and Firefox (right)

Repeat the same steps on the html node to take a full-page screenshot. When you do, though, it’s worth noting that Safari retains the transparency of the element’s background color — Chromium and Firefox will capture it as a white background.

Two screenshots of the same element, one with a background and one without.
Comparing screenshots in Safari (left) and Chromium (right)

There’s another option! You can take a “responsive” screenshot of the page, which allows you to capture the page at a specific viewport width. As you might expect, each browser has different ways to get there.

  • Chromium: Cmd + Shift + M (macOS) or Ctrl + Shift + M (Windows). Or click the “Devices” icon next to the “Inspect” icon.
  • Firefox: Tools → Browser Tools → “Responsive Design Mode”
  • Safari: Develop → “Enter Responsive Design Mode”
Enter responsive mode options in DevTools for all three browsers.
Launching responsive design mode in Safari (left), Firefox (right), and Chromium (bottom)

Chrome tip: Inspect the top layer

Chrome lets you visualize and inspect top-layer elements, like a dialog, alert, or modal. When an element is added to the #top-layer, it gets a top-layer badge next to it, which on click, jumps you to the top-layer container located just after the </html> tag.

The order of the elements in the top-layer container follows the stacking order, which means the last one is on the top. Click the reveal badge to jump back to the node.

Firefox tip: Jump to ID

Firefox links the element referencing the ID attribute to its target element in the same DOM and highlights it with an underline. Use CMD + Click (macOS) or CTRL + Click (Windows) )to jump to the target element with the identifier.

Wrapping up

Quite a few things, right? It’s awesome that there are some incredibly useful DevTools features that are supported in Chromium, Firefox, and Safari alike. Are there any other lesser-known features supported by all three that you like?

There are a few resources I keep close by to stay on top of what’s new. I thought I’d share them with here:


Some Cross-Browser DevTools Features You Might Not Know originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/some-cross-browser-devtools-features-you-might-not-know/feed/ 0 377264
The Making of Atomic CSS: An Interview With Thierry Koblentz https://css-tricks.com/thierry-koblentz-atomic-css/ https://css-tricks.com/thierry-koblentz-atomic-css/#comments Thu, 03 Feb 2022 15:24:01 +0000 https://css-tricks.com/?p=362578 I interviewed Thierry Koblentz, creator of Atomic CSS, to understand the history and background that led to making of the popular CSS framework. Thierry, now retired, has vast experience writing CSS at large scale and has previously worked …


The Making of Atomic CSS: An Interview With Thierry Koblentz originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I interviewed Thierry Koblentz, creator of Atomic CSS, to understand the history and background that led to making of the popular CSS framework. Thierry, now retired, has vast experience writing CSS at large scale and has previously worked as a front-end engineer at Yahoo!.

Thierry is widely credited with bringing the concept of Atomic CSS to the mainstream, thanks to his now classic 2013 article on Smashing Magazine, “Challenging CSS Best Practices.” That article paved the way for many popular CSS libraries over the years. In this interview, Thierry returns to chronicle the history of Atomic CSS and reflect on its ongoing legacy.

Photo of Thierry Koblentz smiling.
Thierry Koblentz

Rolling back the years to the early 2000’s, how did you get into web development, especially writing CSS to make a living?

Thierry Koblentz: I taught myself HTML, CSS, and JavaScript as a hobby after moving to the U.S. in 1997.

At the time, I was using FrontPage and was relying heavily on Newsgroups for guidance. I quickly became a regular on Macromedia NewsGroups and on CSS-Discuss. Early on, I espoused the philosophy of the Web Standard Project and got really interested in Accessibility. For years, front-end was nothing more than a hobby for me (my real job was an antique dealer). I would create a website once in a while but I was mostly writing and publishing (many) articles, sharing techniques I’d learned or “discovered.”

This paid off in the form of a phone call from Yahoo! in 2007, asking if I could help fixing and building stylesheets for the Yahoo! Site Solutions (YSS) website builder template. The job description: no HTML, no JavaScript, just CSS! And a lot of it!

What was your day job at Yahoo! like?

TK: My role at Yahoo! changed a lot through the years.

My first job was to create stylesheets (à la CSS Zen Garden) for the YSS template. I then rewrote the markup and styles of the YSS website just before YSS was “shipped” to Bangalore (India) — where I was sent with my colleagues for “transfer of knowledge” purposes.

As a sidenote, it was the challenge of swapping stylesheets to create different designs for YSS that forced us to find a light (non-js) solution for resizing videos on the fly; and that’s how I came up with “Creating Intrinsic Ratios for Video.”

After YSS, I had the opportunity to only work on projects that started from scratch (rewrites or otherwise) and I got more and more involved with Yahoo! FE. I edited and created many internal docs (i.e. CSS Coding Standards); participated in the hiring process (like everybody else in my team); led code review sessions; ran CSS classes and workshops; spoke at FED London; helped other teams with HTML/CSS/accessibility; was involved in decisions regarding technology adoption (i.e. Bootstrap or not Bootstrap); created libraries; reviewed internal papers; wrote proposals; etc.

Another sidenote, during my eight years at Yahoo!, I may have written less than 100 lines of JavaScript. And if I didn’t quit or get fired from my job, it is thanks to Lingyan Zhu and Renato Iwashima; they helped me tirelessly when it came to setting up my environment or dealing with the command line (because, to this day, I am terrible at that).

TK: In the early days, there were neither libraries nor published methodologies; it was the Wild West, everything went: “non-semantic” classes, IDs, CSS hacks, conditional comments, frames, CSS expressions, “JS sniffing,” designing primarily for Internet Explorer, etc. On my old website, I even had this comment:

<!--MSIE5 Mac needs this comment -->

Everything was fair game and everything was abused as we had a very limited set of tools with the demand to do a lot.

But things had changed dramatically by the time I joined Yahoo!. Devs from the U.K. were strong supporters of Web Standards and I credit them for greatly influencing how HTML and CSS were written at Yahoo!. Semantic markup was a reality and CSS was written following the Separation of Concern (SoC) principle to the “T” (which was overzealous for my liking at times though).

YUI had CSS components but did not have a CSS framework yet. There was an in-house CSS library (called Lego) but I never had to use it. Methodologies and libraries, like OOCSS, SMACSS, ECSS (shoutout to Ben), BEM, Bootstrap, Pure, and others would come shortly after.

What led to the idea of Atomic CSS?

TK: Before YSS was moved to India, my manager, Michael Montesano, asked if there was a way for the new team in Bangalore to avoid having to edit the stylesheet, and thus reducing the risks of breakage. I guess the YSS template experience (paying customers complaining about broken pages) made him pretty paranoid when it came to making any change to a stylesheet.

So I created a “utility-sheet” in the spirit of my ez-css library — a sheet meant to let developers achieve their styling without the need to edit or add rules in a stylesheet.

A couple of years later, Michael, then Director of Engineering, asked me if I could redesign Yahoo!’s Home Page using utility classes only, knowing that, once again, we wouldn’t be in charge of the website maintenance. We talked about prioritizing utility classes over semantic classes, something I don’t think existed at such a level at the time. It was a very bold move.

This large scale exercise quickly became a proof of concept that showed the many benefits that came with styling via markup. It checked so many boxes that it was decided that we’d use that “static” stylesheet (called Stencil) to redesign the Yahoo! My Home Page product.

Screenshot of the Atomic CSS homepage. The background is bright blue with white text that says Atomic CSS on Steroids with a Get Started button below. At the bottom is a small blurb that reads CSS for component-based frameworks.

What were the guiding principles while designing Atomic CSS (ACSS) and who were the people involved?

TK: Our Stencil library being static was a great tool to impose/enforce a design style — which we thought Yahoo! was about to adopt across all its properties. We quickly realized that this was not going to happen. Every Yahoo! design team had their own view of what was the perfect font size, the perfect margin, etc., and we were constantly receiving requests to add very specific styles to the library.

That situation was unmaintainable so we decided to come up with a tool that would let developers create their own styles on the fly, while respecting the Atomic nature of the authoring method. And that’s how Atomizer was born. We stopped worrying about adding styles — CSS declarations — and instead focused on creating a rich vocabulary to give developers a wide array of styling, like media queries, descendant selectors, and pseudo-classes, among other things.

With ACSS, developers were free to use whatever they wanted; hence teams were able to adopt different design styles and styles guides while using the exact same library. And there were some immediate benefits that were new to the way developers were used to writing styles. They no longer had to worry about breaking the page with their styling or worry about writing selectors to style their components.

ACSS was built first and foremost to address Yahoo!’s problems and to work in Yahoo!’s environment.

The people involved with Atomic CSS were Renato Iwashima, Steve Carlson, and myself. Renato and Steve created Atomizer.

What misconceptions do people have about CSS when they don’t write CSS for large enterprises?

TK: When I joined Yahoo! in 2007, I quickly learned how enormous a codebase could be. There were teams working across many locations/timezones; a myriad of products; hundreds of shared components; third-party code; A/B testing strategies; scaling as a requirement; different script directions; localization and internationalization; various release cycles; complex deployment mechanisms; tons of metrics; legacies of all sorts; strict coding standards; build processes; politics; and more politics; etc.

Most of that was totally new to me and I had to learn if and how any of it could influence the way I was writing CSS. I started to revisit and challenge all my beliefs as many techniques or methods that were common practice to me seemed to be unfit, or at least counter-productive, for complex apps.

One “reality check” relates to style abstraction. We all have read articles saying that mapping a M-10 class to margin: 10px was not a good idea as it meant to edit both the HTML and CSS to change the styling. Unfortunately, this is what happens in large/complex projects:

  • Designer: I want a 15px gap
  • Developer: OK, that’s M-3x (5px increment)
  • Designer: Sure, whatever!
  • Developer: Done!
  • Designer: Actually, 15px is a bit too big, can you make it 12px?
  • Developer: No, we don’t have that, it’s either 10px or 15px.
  • Designer: Sorry, that doesn’t work for me. Can we change M-3x to be 12px?
  • Developer: Nope! We can’t do that because other teams expect M-3x to be 15px.
  • Designer: OK, try to figure a way because we want the margin to be 12px. 15px is too much and 10px is too little.
  • Developer: (F*ck this!)

To anticipate such a problem, one needs to understand the designer’s intent behind their request: is the style chosen because of its abstraction, e.g. color primary, or for its specific value, e.g. a margin of 15px in our M-3x case? If a style guide exists to enforce design principles, then classes like M-3x may be OK, but if design teams can request any style they want, then it is much safer to stay away from naming conventions that will lead to ambiguous styling. In my experience, anything ambiguous leads, sooner or later, to breakages.

Relying on the structure of a document or component for its styling — via combinators like > or + — sounds like a clean approach to authoring a stylesheet, but it is ignoring the fact that in a complex environment one cannot assume any specific markup, or construct, to be immutable.

You think z-index is complicated? Think again when you do not even own the scope of the stack your component lives in. That’s one of the most complex issues to address in a large project where teams are in charge of different parts of the page. I once wrote a proposal about this.

Qualifying selectors — like input.required vs. .input.required — may look good and semantic but it creates an unnecessary specificity level — like 0.1.1 vs. 0.2.0 — and prevents markup change; two things easy to avoid by making sure you do not qualify your selector.

Relying on the universal selector, *, for styling global scope? In a very large project, it could mean you are styling someone else’s component. Don’t make styling decisions for people unless you know their requirements.

I am sure you have read that IDs are bad and that specificity is evil but. in fact. high specificity is not as much of a problem as the number of specificity levels your rules create. It is much easier to style within an environment where only two or three levels exist — like 1.1.0, 0.1.0, 0.2.0 — rather than an environment where specificity is rather low but follows a “free for all” approach — like 0.1.0, 0.1.1, 0.2.0, 0.2.1, 0.2.2, etc. — which often comes as a defensive mechanism in large projects as a mean to “sandbox” styles.

Blindly following advice from the CSS community may lead to unpleasant surprises. Never jump on new techniques that have not yet been battle tested. Remember will-change? And always know what every style you use does or may trigger. For example, position can create a stacking context and a containing block, while overflow can create a block-formatting context.

In my experience, knowing CSS inside-out is not enough to write CSS efficiently for a large organization. During my tenure at Yahoo!, I often found myself in contradiction with people I used to be aligned with years before. The environment is brutal and one needs to be very pragmatic to avoid many pitfalls. Next time you look at the source code of a large project and see something that makes no sense to you, remember this tweet from Nicholas Zakas:

How was Yahoo!’s transition to Atomic CSS received internally?

TK: ACSS was well accepted by our My Home Page team, but it didn’t go well outside of that. Our first interaction was with the Sports team based in Santa Monica. Steve and I were in a conference call trying to convince the developers that not following the Separation of Concern’ principle was the way to go and that it would not create chaos.

We pointed them to a piece that Nicolas Gallagher had recently written, thinking that an article from an “outsider” would help, but nope! Things didn’t go well and there was a lot of friction. The main issue was the fact that the library was made of utility classes, but its syntax did not help to ease the conversation.

I recall also meeting with the Mail team who didn’t push back on the idea of Atomic CSS, but wanted to come up with their own JavaScript approach to use “plain” CSS declarations — as they could not stand the ACSS syntax. In any case, the data in favor of the library (~36% less CSS and HTML) was speaking for itself, so ACSS was eventually adopted. And today, seven-plus years later, Yahoo! Home Page, Yahoo! Sports, Yahoo! News, Yahoo! Finance, and other Yahoo! Products are all still using ACSS.

To better understand how an approach like ACSS can benefit projects where component reusability is paramount, copy the markup of a component from Yahoo! Finance and paste it inside Yahoo! News. That component should look like it belongs to the page. This is because ACSS makes these components page agnostic.

How did the idea of using parentheses for class names manifest? Was the syntax inspired from how functions are written?

TK: We had identified — through many iterations — two sets of “candidates” to be used as delimiters for property values: parentheses, (), and brackets, [].

Renato remembers that we picked parentheses over brackets because of familiarity with functions in JavaScript, even if it came at the cost of an extra Shift keystroke. The ACSS syntax was designed to:

  • facilitate the automatic generation of rules, via Atomizer
  • allow developers to create any arbitrary or complex styles they want
  • reduce the learning curve to a minimum

It looks like this:

[<context>[:<pseudo-class>]<combinator>]<Style>[(<value>,<value>?,...)][<!>][:<pseudo-class>][::<pseudo-element>][--<breakpoint_identifier>]

Developers build their classes following the above construct. The core syntax is based on Emmet, a popular toolkit. We adopted the Emmet approach to reduce idiosyncrasies as core classes are explicit property/value pairs rather than arbitrary strings.

We also created a dozen of helper classes. Those apply multiple style declarations and are either shortcuts, like hiding content from sighted users, or hacks, like using .Cf for clearfix. And we gave developers even more latitude through the use of a config file in which they can create variables — like .PrimaryColor — breakpoints, and much more.

People who’ve never worked with ACSS will tell you that the syntax is too weird (at best), but people familiar with it will tell you it’s clever in many ways.

Talk about how your “Challenging CSS Best Practices” article for Smashing Magazine came to fruition?

TK: I had written many articles in various online publications before, so it was natural for me to write an article about this “challenging” approach.

Yahoo! was sponsoring a front-end conference in October 2013 where Renato had a talk scheduled to present our solution, and I was trying to get the article published before that. I chose to not publish it on Yahoo! Developer Network because the website did not offer a comment section. A List Apart could not publish it in time, but Smashing Magazine accelerated its review process to be able to publish the piece before the end of October.

My choice of going with a publisher who had a comment section paid off as the article received 200-plus comments which turned out to be very time consuming — and frustrating — for me who had to respond to them.

Does it feel strange that the article still carries the disclaimer about the techniques discussed, even though it is widely popular in the industry now?

TK: When the article was published, I told Vitaly [Friedman, Smashing Magazine co-founder] that that note sounded like some type of a disclaimer to me; that it would sway people in their reading of the article. But I didn’t really push back as I understood where Vitaly was coming from. I do find it amusing that note is still there now this methodology has become mainstream.

Given that hindsight is 20/20, is there anything that you want to change about Atomic CSS?

TK: There is always room for improvement, even more so when you’ve pioneered the solution. You can’t look at what others have done to learn from their mistakes or shortcomings. You don’t have material to improve upon. So, it’d be pretentious for us to think we nailed it on our first try.

On the Atomic CSS side, we had a lot of experience for having developed and used a “static” stylesheet on a large project for more than a year. But on the dynamic side — the tooling side — it’s not like we could find much inspiration out there. Remember that it took six years for other libraries to follow suit.

In French, we say: essuyer les plâtres.

One mistake we made was to use “Atomic CSS” as the title for acss.io, because as John Polacek pointed out, it created some confusion. We’ve changed that title since then.

The only regret I have is how the community has treated Atomic CSS/ACSS through the years, which recently lead to a weird exchange, where somebody explained to me what “Atomic CSS” means:

The Atomic CSS library [ACSS] uses the name but I think this is misleading, because the feature you’re talking about is the dynamic style generation. “Atomic CSS” as a generic term designates small selectors as atoms, but they’re static.

Talk about being erased. ;)


A big thanks to Thierry for participating in this interview and allowing us to publish it for the community.


The Making of Atomic CSS: An Interview With Thierry Koblentz originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/thierry-koblentz-atomic-css/feed/ 2 362578
Building a Blog with Next.js https://css-tricks.com/building-a-blog-with-next-js/ https://css-tricks.com/building-a-blog-with-next-js/#comments Thu, 09 Jul 2020 14:52:06 +0000 https://css-tricks.com/?p=316504 In this article, we will use Next.js to build a static blog framework with the design and structure inspired by Jekyll. I’ve always been a big fan of how Jekyll makes it easier for beginners to setup a blog …


Building a Blog with Next.js originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
In this article, we will use Next.js to build a static blog framework with the design and structure inspired by Jekyll. I’ve always been a big fan of how Jekyll makes it easier for beginners to setup a blog and at the same time also provides a great degree of control over every aspect of the blog for the advanced users.

With the introduction of Next.js in recent years, combined with the popularity of React, there is a new avenue to explore for static blogs. Next.js makes it super easy to build static websites based on the file system itself with little to no configuration required.

The directory structure of a typical bare-bones Jekyll blog looks like this:

.
├─── _posts/          ...blog posts in markdown
├─── _layouts/        ...layouts for different pages
├─── _includes/       ...re-usable components
├─── index.md         ...homepage
└─── config.yml       ...blog config

The idea is to design our framework around this directory structure as much as possible so that it becomes easier to  migrate a blog from Jekyll by simply reusing the posts and configs defined in the blog.

For those unfamiliar with Jekyll, it is a static site generator that can transform your plain text into static websites and blogs. Refer the quick start guide to get up and running with Jekyll.

This article also assumes that you have a basic knowledge of React. If not, React’s getting started page is a good place to start.

Installation

Next.js is powered by React and written in Node.js. So we need to install npm first, before adding next, react and react-dom to the project.

mkdir nextjs-blog && cd $_
npm init -y
npm install next react react-dom --save

To run Next.js scripts on the command line, we have to add the next command to the scripts section of our package.json.

"scripts": {
  "dev": "next"
}

We can now run npm run dev on the command line for the first time. Let’s see what happens.

$ npm run dev
> nextjs-blog@1.0.0 dev /~user/nextjs-blog
> next

ready - started server on http://localhost:3000
Error: > Couldn't find a `pages` directory. Please create one under the project root

The compiler is complaining about a missing pages directory in the root of the project. We’ll learn about the concept of pages in the next section.

Concept of pages

Next.js is built around the concept of pages. Each page is a React component that can be of type .js or .jsx which is mapped to a route based on the filename. For example:

File                            Route
----                            -----
/pages/about.js                 /about
/pages/projects/work1.js        /projects/work1
/pages/index.js                 /

Let’s create the pages directory in the root of the project and populate our first page, index.js, with a basic React component.

// pages/index.js
export default function Blog() {
  return <div>Welcome to the Next.js blog</div>
}

Run npm run dev once again to start the server and navigate to http://localhost:3000 in the browser to view your blog for the first time.

Screenshot of the homepage in the browser. The content says welcome to the next.js blog.

Out of the box, we get:

  • Hot reloading so we don’t have to refresh the browser for every code change.
  • Static generation of all pages inside the /pages/** directory.
  • Static file serving for assets living in the/public/** directory.
  • 404 error page.

Navigate to a random path on localhost to see the 404 page in action. If you need a custom 404 page, the Next.js docs have great information.

Screenshot of the 404 page. It says 404 This page could not be found.

Dynamic pages

Pages with static routes are useful to build the homepage, about page, etc. However, to dynamically build all our posts, we will use the dynamic route capability of Next.js. For example:

File                        Route
----                        -----
/pages/posts/[slug].js      /posts/1
                            /posts/abc
                            /posts/hello-world

Any route, like /posts/1, /posts/abc, etc., will be matched by /posts/[slug].js and the slug parameter will be sent as a query parameter to the page. This is especially useful for our blog posts because we don’t want to create one file per post; instead we could dynamically pass the slug to render the corresponding post.

Anatomy of a blog

Now, since we understand the basic building blocks of Next.js, let’s define the anatomy of our blog.

.
├─ api
│  └─ index.js             # fetch posts, load configs, parse .md files etc
├─ _includes
│  ├─ footer.js            # footer component
│  └─ header.js            # header component
├─ _layouts
│  ├─ default.js           # default layout for static pages like index, about
│  └─ post.js              # post layout inherts from the default layout
├─ pages
│  ├─ index.js             # homepage
|  └─ posts                # posts will be available on the route /posts/
|     └─ [slug].js       # dynamic page to build posts
└─ _posts
   ├─ welcome-to-nextjs.md
   └─ style-guide-101.md

Blog API

A basic blog framework needs two API functions: 

  • A function to fetch the metadata of all the posts in _posts directory
  • A function to fetch a single post for a given slug with the complete HTML and metadata

Optionally, we would also like all the site’s configuration defined in config.yml to be available across all the components. So we need a function that will parse the YAML config into a native object.

Since, we would be dealing with a lot of non-JavaScript files, like Markdown (.md), YAML (.yml), etc, we’ll use the raw-loader library to load such files as strings to make it easier to process them. 

npm install raw-loader --save-dev

Next we need to tell Next.js to use raw-loader when we import .md and .yml file formats by creating a next.config.js file in the root of the project (more info on that).

module.exports = {
  target: 'serverless',
  webpack: function (config) {
    config.module.rules.push({test:  /\.md$/, use: 'raw-loader'})
    config.module.rules.push({test: /\.yml$/, use: 'raw-loader'})
    return config
  }
}

Next.js 9.4 introduced aliases for relative imports which helps clean up the import statement spaghetti caused by relative paths. To use aliases, create a jsconfig.json file in the project’s root directory specifying the base path and all the module aliases needed for the project.

{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@includes/*": ["_includes/*"],
      "@layouts/*": ["_layouts/*"],
      "@posts/*": ["_posts/*"],
      "@api": ["api/index"],
    }
  }
}

For example, this allows us to import our layouts by just using:

import DefaultLayout from '@layouts/default'

Fetch all the posts

This function will read all the Markdown files in the _posts directory, parse the front matter defined at the beginning of the post using gray-matter and return the array of metadata for all the posts.

// api/index.js
import matter from 'gray-matter'


export async function getAllPosts() {
  const context = require.context('../_posts', false, /\.md$/)
  const posts = []
  for(const key of context.keys()){
    const post = key.slice(2);
    const content = await import(`../_posts/${post}`);
    const meta = matter(content.default)
    posts.push({
      slug: post.replace('.md',''),
      title: meta.data.title
    })
  }
  return posts;
}

A typical Markdown post looks like this:

---
title:  "Welcome to Next.js blog!"
---
**Hello world**, this is my first Next.js blog post and it is written in Markdown.
I hope you like it!

The section outlined by --- is called the front matter which holds the metadata of the post like, title, permalink, tags, etc. Here’s the output:

[
  { slug: 'style-guide-101', title: 'Style Guide 101' },
  { slug: 'welcome-to-nextjs', title: 'Welcome to Next.js blog!' }
]

Make sure you install the gray-matter library from npm first using the command npm install gray-matter --save-dev.

Fetch a single post

For a given slug, this function will locate the file in the _posts directory, parse the Markdown with the marked library and return the output HTML with metadata.

// api/index.js
import matter from 'gray-matter'
import marked from 'marked'


export async function getPostBySlug(slug) {
  const fileContent = await import(`../_posts/${slug}.md`)
  const meta = matter(fileContent.default)
  const content = marked(meta.content)    
  return {
    title: meta.data.title, 
    content: content
  }
}

Sample output:

{
  title: 'Style Guide 101',
  content: '<p>Incididunt cupidatat eiusmod ...</p>'
}

Make sure you install the marked library from npm first using the command npm install marked --save-dev.

Config

In order to re-use the Jekyll config for our Next.js blog, we’ll parse the YAML file using the js-yaml library and export this config so that it can be used across components.

// config.yml
title: "Next.js blog"
description: "This blog is powered by Next.js"


// api/index.js
import yaml from 'js-yaml'
export async function getConfig() {
  const config = await import(`../config.yml`)
  return yaml.safeLoad(config.default)
}

Make sure you install js-yaml from npm first using the command npm install js-yaml --save-dev.

Includes

Our _includes directory contains two basic React components, <Header> and <Footer>, which will be used in the different layout components defined in the _layouts directory.

// _includes/header.js
export default function Header() {
  return <header><p>Blog | Powered by Next.js</p></header>
}


// _includes/footer.js
export default function Footer() {
  return <footer><p>©2020 | Footer</p></footer>
}

Layouts

We have two layout components in the _layouts directory. One is the <DefaultLayout> which is the base layout on top of which every other layout component will be built.

// _layouts/default.js
import Head from 'next/head'
import Header from '@includes/header'
import Footer from '@includes/footer'


export default function DefaultLayout(props) {
  return (
    <main>
      <Head>
        <title>{props.title}</title>
        <meta name='description' content={props.description}/>
      </Head>
      <Header/>
      {props.children}
      <Footer/>
    </main>
  )
}

The second layout is the <PostLayout> component that will override the title defined in the <DefaultLayout> with the post title and render the HTML of the post. It also includes a link back to the homepage.

// _layouts/post.js
import DefaultLayout from '@layouts/default'
import Head from 'next/head'
import Link from 'next/link'


export default function PostLayout(props) {
  return (
    <DefaultLayout>
      <Head>
        <title>{props.title}</title>
      </Head>
      <article>
        <h1>{props.title}</h1>
        <div dangerouslySetInnerHTML={{__html:props.content}}/>
        <div><Link href='/'><a>Home</a></Link></div> 
      </article>
    </DefaultLayout>
  )
}

next/head is a built-in component to append elements to the <head> of the page. next/link is a built-in component that handles client-side transitions between the routes defined in the pages directory.

Homepage

As part of the index page, aka homepage, we will list all the posts inside the _posts directory. The list will contain the post title and the permalink to the individual post page. The index page will use the <DefaultLayout> and we’ll import the config in the homepage to pass the title and description to the layout.

// pages/index.js
import DefaultLayout from '@layouts/default'
import Link from 'next/link'
import { getConfig, getAllPosts } from '@api'


export default function Blog(props) {
  return (
    <DefaultLayout title={props.title} description={props.description}>
      <p>List of posts:</p>
      <ul>
        {props.posts.map(function(post, idx) {
          return (
            <li key={idx}>
              <Link href={'/posts/'+post.slug}>
                <a>{post.title}</a>
              </Link>
            </li>
          )
        })}
      </ul>
    </DefaultLayout>
  )
} 


export async function getStaticProps() {
  const config = await getConfig()
  const allPosts = await getAllPosts()
  return {
    props: {
      posts: allPosts,
      title: config.title,
      description: config.description
    }
  }
}

getStaticProps is called at the build time to pre-render pages by passing props to the default component of the page. We use this function to fetch the list of all posts at build time and render the posts archive on the homepage.

Screenshot of the homepage showing the page title, a list with two post titles, and the footer.

Post page

This page will render the title and contents of the post for the slug supplied as part of the context. The post page will use the <PostLayout> component.

// pages/posts/[slug].js
import PostLayout from '@layouts/post'
import { getPostBySlug, getAllPosts } from "@api"


export default function Post(props) {
  return <PostLayout title={props.title} content={props.content}/>
}


export async function getStaticProps(context) {
  return {
    props: await getPostBySlug(context.params.slug)
  }
}


export async function getStaticPaths() {
  let paths = await getAllPosts()
  paths = paths.map(post => ({
    params: { slug:post.slug }
  }));
  return {
    paths: paths,
    fallback: false
  }
}

If a page has dynamic routes, Next.js needs to know all the possible paths at build time. getStaticPaths supplies the list of paths that has to be rendered to HTML at build time. The fallback property ensures that if you visit a route that does not exist in the list of paths, it will return a 404 page.

Screenshot of the blog page showing a welcome header and a hello world blue above the footer.

Production ready

Add the following commands for build and start in package.json, under the scripts section and then run npm run build followed by npm run start to build the static blog and start the production server.

// package.json
"scripts": {
  "dev": "next",
  "build": "next build",
  "start": "next start"
}

The entire source code in this article is available on this GitHub repository. Feel free to clone it locally and play around with it. The repository also includes some basic placeholders to apply CSS to your blog.

Improvements

The blog, although functional, is perhaps too basic for most average cases. It would be nice to extend the framework or submit a patch to include some more features like:

  • Pagination
  • Syntax highlighting
  • Categories and Tags for posts
  • Styling

Overall, Next.js seems really very promising to build static websites, like a blog. Combined with its ability to export static HTML, we can built a truly standalone app without the need of a server!


Building a Blog with Next.js originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/building-a-blog-with-next-js/feed/ 7 316504
Password Strength `meter` https://css-tricks.com/password-strength-meter/ https://css-tricks.com/password-strength-meter/#comments Wed, 02 Dec 2015 16:19:16 +0000 http://css-tricks.com/?p=235493 The following is a guest post by Pankaj Parashar. Pankaj is our resident expert on all things <progress> and <meter> and this post is more evidence of that. Here, he walks us through implementing a password strength meter using


Password Strength `meter` originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
The following is a guest post by Pankaj Parashar. Pankaj is our resident expert on all things <progress> and <meter> and this post is more evidence of that. Here, he walks us through implementing a password strength meter using what is likely the semantically best option.

A number of major websites like Dropbox, Gmail and eBay, rely on some kind of an indicator to indicate the strength of the password to the user during registration. The indicator serves as a good reminder for the user as to the level of difficulty to crack the password.

Showcasing the password strength indicators from eBay(Top), Gmail(Middle) and Dropbox(Bottom) in varying forms, essentially representing the same information.

Although this practice is not new, most of the implementation for the password strength indicator I’ve seen so far uses the same old markup of <div> and <span> to represent the indicator. With the introduction of HTML5, we now have the ability to use the <meter> element in our markup, which in my opinion, is semantically more accurate and perfectly suitable for this password strength indicator use-case. Throughout this article, we’ll call it the password strength meter.

Why not use the HTML5 <progress> element?

As the name suggests, the HTML5 <progress> element is used to indicate the progress of a task or an activity. Representing the strength of the password, isn’t really a task or activity. It’s not something that has progress towards a goal or is ever complete. Hence, it is safe to conclude that the <progress> element is not the right candidate for this use-case.

How to calculate the strength of a password?

We’ll be using the zxcvbn library by Dropbox to calculate the strength of the password. There are quite a few alternative JavaScript libraries that computes the strength of a password, but zxcvbn is perfect for our use case because:

  1. It provides a simple API that takes a password as the input and returns a score from 0 to 4 as the output to indicate the strength of a password (0 – weakest, 4 – strongest). This works quite well with our <meter> element, that can accept a value within a predefined minmax range.
  2. It estimates a realistic strength of the password, by detecting all the possible overlapping patterns and then matches it against several English dictionaries, common passwords, keyboard patterns and repetitions.
  3. It is built by Dropbox! The official blog provides much more information on the technical details about the algorithm.

If you are not familiar with the HTML5 <meter> element, then CSS-Tricks has just the right article, to get you up and running with the basics. I would strongly recommend reading it before you proceed with this article.

Basic markup

Let’s start with a basic markup: an <input> field that accepts a password, with a paired <label>.

<label for="password">Enter password</label>
<input type="password" id="password" required>

We’ll add the <meter> element below the <input> along with a text element where we can affirm and explain the current value represented by the meter element. Since, zxcvbn returns a value in range of 0 to 4, the minimum possible value of the meter is 0 whereas the maximum possible value is 4. If not specified, the default value of the min attribute is always 0.

<meter max="4" id="password-strength-meter"></meter>
<p id="password-strength-text"></p>

W3C recommends including a textual representation of the current value inside the meter tag for older browsers. However, we’ll keep it blank for now and discuss the possible fallback techniques later in the article.

Styling the meter

In this section, we will only focus on styling the <meter> element. I’ll leave the styling of the rest of the markup as an exercise for you. In order to understand how to style the <meter> element, we need to peek under the hood of the browsers to deconstruct the implementation of the <meter> element.

For example, this is how Blink/Webkit and Gecko based browsers decompose the <meter> tag:

Illustrating the implementation level detail of the HTML5 <meter> element in the various rendering engines.

The zxcvbn library returns a score from 0 to 4. This means that there are five possible values for our password strength meter. We can target each one of them using the attribute selector, eg., meter[value="0"], meter[value="1"] etc.,

The score represents the strength of the password. The higher the score, the more difficult is the password to crack. We will represent each score with a different color to provide a visual feedback to the user. We can skip styling the value="0", as it will not be visible.

Styling the meter bar

meter {
  /* Reset the default appearance */
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;

  margin: 0 auto 1em;
  width: 100%;
  height: 0.5em;

  /* Applicable only to Firefox */
  background: none;
  background-color: rgba(0, 0, 0, 0.1);
}

meter::-webkit-meter-bar {
  background: none;
  background-color: rgba(0, 0, 0, 0.1);
}

Styling the meter value

/* Webkit based browsers */
meter[value="1"]::-webkit-meter-optimum-value { background: red; }
meter[value="2"]::-webkit-meter-optimum-value { background: yellow; }
meter[value="3"]::-webkit-meter-optimum-value { background: orange; }
meter[value="4"]::-webkit-meter-optimum-value { background: green; }

/* Gecko based browsers */
meter[value="1"]::-moz-meter-bar { background: red; }
meter[value="2"]::-moz-meter-bar { background: yellow; }
meter[value="3"]::-moz-meter-bar { background: orange; }
meter[value="4"]::-moz-meter-bar { background: green; }

Updating the meter

Before we proceed, lets include the zxcvbn library from cdnjs right before the body element using a <script> tag.

<script src="https://cdnjs.cloudflare.com/ajax/libs/zxcvbn/4.2.0/zxcvbn.js"></script>

zxcvbn adds a single function to the global namespace. It takes one required argument (a password) and returns a resultant object with the following properties:

  • guesses – Estimated no. of guesses needed to crack the password
  • guesses_log10 – Logarithmic value of guesses to the base 10
  • crack_time_seconds – Estimation of the actual crack time, in seconds
  • crack_time_display – Crack time in a human readable format, like, “3 hours” etc
  • score – Integer in a range 0-4 (this is what we will be using for the meter)
  • feedback.warning – Explains what’s wrong with the entered password
  • feedback.suggestions – List of suggestions to help choose a less guessable password
  • sequence – List of patterns that zxcvbn based the guess calculation on
  • calc_time – The time it took the zxcvbn to calculate an answer, in milliseconds

zxcvbn also includes an optional user_inputs argument which accepts an array of strings that it uses as a blacklist to penalize passwords containing user’s personal information from other fields like name, email etc.

Now every time the password field is changed, we will pass the password to the zxcvbn function and update the meter using the resultant score. In addition to updating the value attribute of the meter, we will also update the accompanying text to indicate the strength of the password to the user.

Firstly, we will map the scores to a human readable format,

var strength = {
  0: "Worst",
  1: "Bad",
  2: "Weak",
  3: "Good",
  4: "Strong"
}

Secondly, attach a listener to the password field that will listen for the changes and then update the meter and the text indicator.

var password = document.getElementById('password');
var meter = document.getElementById('password-strength-meter');
var text = document.getElementById('password-strength-text');

password.addEventListener('input', function() {
  var val = password.value;
  var result = zxcvbn(val);

  // Update the password strength meter
  meter.value = result.score;

  // Update the text indicator
  if (val !== "") {
    text.innerHTML = "Strength: " + strength[result.score]; 
  } else {
    text.innerHTML = "";
  }
});

The demo Pen additionally employs feedback.warnings and feedback.suggestions to provide a relevant and useful feedback to the user, to help them choose a less-guessable password.

See the Pen Password strength meter by Pankaj Parashar (@pankajparashar) on CodePen.

If for any reason the demo fails in your browser, you can watch this video.

Fallback

As it stands, our password strength meter works quite well in all the browsers that support the HTML5 <meter> element. The good thing is, we do not have to worry about the browsers that don’t support it. Those browsers will simply ignore the <meter> tag and render the text after the meter, which is a decent fallback to indicate the strength of the password to the user.

If you’re determined to provide a consistent user experience across all the browsers, you could simulate the appearance of the meter using a combination of <div> and <span> markup inside the <meter> element. Browsers that do not understand the <meter> tag will simply ignore it and instead render the markup inside. I’ve described this method in detail in the fallback section of my previous article on CSS-Tricks on the same topic.

Are password strength meters good from the UX point of view?

This article doesn’t intend to spark a debate on whether password strength meters are good or not. There are probably, rational and reasonable arguments on both the sides. However, most of the argument stems from the inability of the algorithm to provide a realistic measure of the strength of the password. I think Dropbox has nailed it with the zxcvbn library because it offers a much more realistic estimation of how difficult is the password to crack.

Whether you use it in your design or not, is up to you. But if you decide to take the plunge, then make sure you use the HTML5 <meter> element!


Password Strength `meter` originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/password-strength-meter/feed/ 12 235493
Reading Position Indicator https://css-tricks.com/reading-position-indicator/ https://css-tricks.com/reading-position-indicator/#comments Wed, 07 May 2014 22:19:44 +0000 http://css-tricks.com/?p=169448 Lately I’ve seen quite a few websites that have some kind of an indicator to display the current reading position (how much you have “read”, depending on how far you have scrolled down an article). Generally, such indicators are used …


Reading Position Indicator originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Lately I’ve seen quite a few websites that have some kind of an indicator to display the current reading position (how much you have “read”, depending on how far you have scrolled down an article). Generally, such indicators are used on blog posts or long form articles and help readers understand how far they are from finishing the article.

Here are some examples:

(View Full Size)
1) Stammy’s blog uses a red color progress bar
2) Ben Frain’s website displays the number of words left
3) Information Architects show “minutes left” to indicate the current reading position.

Interestingly, all three techniques represent the same information but with a different approach. I don’t know if there is a name for this feature – so throughout the article, I call it a Reading Position Indicator.

In this article, we’ll focus on the first technique that uses a horizontal progress bar as the indicator. But instead of using traditional div/span(s) and some non-linear math to build the indicator, we will use the HTML5 progress element. In my opinion is much more semantically accurate and suitable to represent this information, and that too with no complex calculations involved.

If you have never used the HTML5 progress element before, then I would strongly recommend you to read my article on CSS-Tricks that gives you an introduction on how to use this element in your markup and style them via CSS as cross-browser as possible with decent fallback techniques

The Problem

To build a reading position indicator, we need to answer two important questions:

  1. What is the length of the webpage? The length of the webpage is same as the length of the document, which can be calculated via JavaScript.
  2. What is the current reading position of the user? Determining the current reading position of the user would entail hacking into the user’s mind to extract the portion of the document currently being read by the user. This appears more like a candidate for Artificial Intelligence and seems impossible; given the scope of technologies that we are dealing with.

This leaves us with no choice but to tackle this problem statement with a completely different approach.

Principle

The principle behind this technique is based on a simple fact that the user needs to scroll to reach the end of the web page. Once the user reaches the end of the web page we can conclude that he/she has finished reading the article. Our technique revolves around the scroll event which is likely to be the key to determine an approximate position of the user while reading.

Assuming the user starts reading from the top and will only scroll once he/she reaches the end of the viewport, we’ll attempt to answer the following questions:

  1. How much the user needs to scroll to reach the end of the web page? The portion of page that is hidden from the viewport is exactly the amount of scroll the user needs to perform to reach the end of the page. This will become our max attribute.
  2. How much portion of the page, user has already scrolled? This can be determined by calculating the vertical offset of the top of the document from the top of the window which will become our value attribute.
A demo simulating the scrolling behaviour of the user. As soon as the user starts scrolling in the downward direction to access the hidden part of the web page, the vertical offset increases. Demo on CodePen

In the context of the browser, document and window are two different objects. window is the viewable area of the browser (thick blue box in the above example) and document is actually the page that loads inside the window (thin grey box currently scrolling).

Markup

Let’s start with a basic markup:

<progress value="0"></progress>

It’s important to explicitly specify the value attribute. Otherwise, our progress bar will be in the indeterminate state. We don’t want to add unnecessary styles in CSS for the indeterminate state. Thus we choose to ignore this state by specifying the value attribute. Initially, the user starts reading from the top, hence, the starting value set in the markup is 0. The default value of the max attribute (if unspecified) is 1.

To determine the correct value for the max attribute, we need to subtract the window’s height from the height of the document. This can only be done via JavaScript, so we will worry about it at a later stage.

The placement of the markup in the HTML document would heavily depend on the how rest of the elements are placed. Typically, if you have no fixed position containers in your document, then you can place the progress element right on top of all the elements inside the tag.

<body>
  <progress value="0"></progress>

  <!--------------------------------
  Place the rest of your markup here
  --------------------------------->
</body>

Styling the indicator

Since, we want our indicator to always sit on top of the web page, even when the user scrolls, we’ll position the progress element as fixed. Additionally, we would want the background of our indicator to be transparent so that an empty progress bar doesn’t create a visual hinderance while scrolling through the web page. At the same time this will also help us tackle browsers with JavaScript disabled that we’ll cover later.

progress {
  /* Positioning */
  position: fixed;
  left: 0;
  top: 0;

  /* Dimensions */
  width: 100%;
  height: 5px;

  /* Reset the appearance */
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;

  /* Get rid of the default border in Firefox/Opera. */
  border: none;

  /* Progress bar container for Firefox/IE10+ */
  background-color: transparent;

  /* Progress bar value for IE10+ */
  color: red;
}

For Blink/Webkit/Firefox, we need to use vendor specific pseudo elements to style the value inside the progress bar. This will be used to add color to our indicator.

progress::-webkit-progress-bar {
  background-color: transparent;
}

progress::-webkit-progress-value {
  background-color: red;
}

progress::-moz-progress-bar {
  background-color: red;
}

Interaction

Calculating the width/height of window and document in JavaScript is messy and varies horribly across different breed of browsers. Thankfully, jQuery manages to abstract all the complexities offered by these browsers and provides a much cleaner mechanism to calculate the dimensions of window and document. Hence, for the rest of the article we’ll rely on jQuery to handle all our interactions with the user.

Before, we begin, do not forget to add jQuery library to your document.

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>

We need jQuery to determine the max and the value attribute of our progress element.

  • max – The max value is the portion of the document that lies outside the viewport which can be calculated by subtracting the window’s height from the height of the document.
    var winHeight = $(window).height(),   docHeight = $(document).height();   max = docHeight - winHeight; $("progress").attr('max', max);
  • value – Initially, value will be zero (already defined in the markup). However, as soon as the user starts scrolling, the vertical offset of the top of the document from the top of the window will increase. If the scrollbar is at the very top, or if the element is not scrollable, the offset will be 0.
    var value = $(window).scrollTop(); $("progress").attr('value', value);
Instead of using document in $(document).height(), we can use other elements like section, article or div that holds the content of the article to calculate the height and present the user with a much more accurate representation of the reading position indicator. This becomes quite useful, when you have a blog post that is filled with comments and constitutes more than 50% of the actual article.

Now, everytime the user scrolls, we need to re-calculate the y-offset from the top of the window and then set it to the value attribute of the progress element. Note that the max attribute remains the same and doesn’t change when the user scrolls.

$(document).on('scroll', function() {
  value = $(window).scrollTop();
  progressBar.attr('value', value);
});

The direction in which the user is scrolling is not important because we always calculate the y-offset from the top of the window.

It’s important that our code executes only then the DOM is loaded, otherwise, premature calculation of window/document’s height could lead to weird and unpredictable results.

$(document).on('ready', function() {  
  var winHeight = $(window).height(), 
      docHeight = $(document).height(),
      progressBar = $('progress'),
      max, value;

  /* Set the max scrollable area */
  max = docHeight - winHeight;
  progressBar.attr('max', max);

  $(document).on('scroll', function(){
     value = $(window).scrollTop();
     progressBar.attr('value', value);
  });
});

(Or ensure this code is loaded at the bottom of the page instead of the top, and skip the document ready call.)

Browser compatibility

This is all what we need to build a functional reading position indicator that works equally well in all the browsers that support the HTML5 progress element. However, the support is limited to Firefox 16+, Opera 11+, Chrome, Safari 6+. IE10+ partially supports them. Opera 11 and 12 doesn’t permit changing the progress bar color. Hence, our indicator reflects the default green color.

Variants

There are quite a few variations possible in which we can style the indicator. Especially, the semantic color scheme (fourth variation) is a useful experiment, wherein the indicator changes color based on the proximity of the reading position from the end of the article.

  • Flat color scheme (default)
  • Single color gradient
  • Multi color gradient
  • Semantic color scheme

Edge cases

There are few scenarios, where our code can potentially break or present the user with an incorrect indicator. Let’s look at those edge cases:

Document height <= Window height

So far, our code assumes that the height of the document is greater than the window’s height, which may not be the case always. Fortunately, browsers handle this situation very well by returning the height of the window, when the document is visibly shorter than the window. Hence, docHeight and winHeight are the same.

max = docHeight - winHeight; // equal to zero.

This is as good as a progress element with both max and value attribute as zero.

<progress value="0" max="0"></progress>

Hence, our progress bar would remain empty and since our background is transparent, there will be no indicator on the page. This makes sense because, when the entire page can fit within the viewport there is really no need for an indicator.

Moreover, the scroll event won’t fire at all because the height of the document doesn’t exceed the window height. Hence, without making any modification, our code is robust enough to handle this edge case.

User resizes the window

When the user resizes the window, the height of the window and the document will change. This means that we will have to recalculate the max and the value attribute to reflect the correct position of the indicator. We’ll bind the code that calculates the correct position to the resize event handler.

$(window).on('resize', function() {
  winHeight = $(window).height(),
  docHeight = $(document).height();

  max = docHeight - winHeight;
  progressBar.attr('max', max);

  value =  $(window).scrollTop();
  progressBar.attr('value', value);
});

Javascript is disabled

When JavaScript is disabled our progress bar would have the default value as 0 and max as 1.

<progress value="0" max="1"></progress>

This would mean that the progress bar would remain empty and wouldn’t affect any part the page. This is as good, as a page with no indicator isn’t a big loss to the reader.

Fallback for older browsers

Older browsers that do not support the HTML5 progress element will simply ignore the progress tag. However, for some devs providing a consistent experience is important. Hence, in the following section, we’ll employ the same fallback technique that was used in my previous article to implement the reading position indicator for oler browsers.

Markup – The idea is to simulate the look and feel of the progress element with div/span(s). Modern browsers will render the progress element and ignore the markup inside it, whereas older browsers that cannot understand the progress element will ignore it and instead render the markup inside it.

<progress value="0">
  <div class="progress-container">
    <span class="progress-bar"></span>
  </div>
</progress>

Styling – The container will always span across the width of the webpage and the background will stay transparent to handle other edge cases.

.progress-container {
  width: 100%;
  background-color: transparent;
  position: fixed;
  top: 0;
  left: 0;
  height: 5px;
  display: block;
}
.progress-bar {
  background-color: red;
  width: 0%;
  display: block;
  height: inherit;
}

Interaction – First we need to separate browsers that do not support the progress element from the browsers that support them. This can be achieved either with native JavaScript or you can use Modernizr to test the feature.

if ('max' in document.createElement('progress')) {
  // Progress element is supported
} else {
  // Doesn't support the progress element. Put your fallback code here. 
}

The inputs still remain the same. But, in addition to determining the value, we need to calculate the width of the .progress-bar in percentage.

winHeight = $(window).height(); 
docHeight = $(document).height();

max = docHeight - winHeight;
value = $(window).scrollTop();

width = (value/max) * 100;
width = width + '%';
    
$('.progress-bar').css({'width': width});

After exploring all the edge cases, we can refactor the code to remove any duplicate statements and make it more DRY-er.

$(document).ready(function() {
    
  var getMax = function(){
    return $(document).height() - $(window).height();
  }
    
  var getValue = function(){
    return $(window).scrollTop();
  }
    
  if ('max' in document.createElement('progress')) {
    // Browser supports progress element
    var progressBar = $('progress');
        
    // Set the Max attr for the first time
    progressBar.attr({ max: getMax() });

    $(document).on('scroll', function(){
      // On scroll only Value attr needs to be calculated
      progressBar.attr({ value: getValue() });
    });
      
    $(window).resize(function(){
      // On resize, both Max/Value attr needs to be calculated
      progressBar.attr({ max: getMax(), value: getValue() });
    }); 
  
  } else {

    var progressBar = $('.progress-bar'), 
        max = getMax(), 
        value, width;
        
    var getWidth = function() {
      // Calculate width in percentage
      value = getValue();            
      width = (value/max) * 100;
      width = width + '%';
      return width;
    }
        
    var setWidth = function(){
      progressBar.css({ width: getWidth() });
    }
        
    $(document).on('scroll', setWidth);
    $(window).on('resize', function(){
      // Need to reset the Max attr
      max = getMax();
      setWidth();
    });
  }
});

Performance

Generally, it is considered a bad practice to attach handlers to the scroll event because the browser attempts to repaint the content that appears every time you scroll. In our case, the DOM structure and the styles applied to them are simple, hence, we wouldn’t observe any lag or noticeable delay while scrolling. However, when we magnify the scale at which this feature can be implemented on websites that employs complex DOM structure with intricate styles, the scroll experience can become janky and the performance may go for a toss.

If scrolling performance is really becoming a big overhead for you to overcome, then you can either choose to get rid of this feature completely or attempt to optimize your code to avoid unnecessary repaints. Couple of useful articles to get you started:

Ambiguity

I am no UX expert, but in some cases, the position and appearance of our indicator can be ambiguous and potentially confuse the user. Ajax-driven websites like Medium, Youtube etc., use similar kind of a progress bar to indicate the load status of the next page. Chrome for mobile natively uses a blue color progress bar for the webpage loader. Now, if you add the reading position indicator to this frame, I am sure that an average user will have a hard time understanding what the progress bar at the top of the page really means.

Website loading bars from Youtube, Medium and Chrome Mobile
Credits to Usability Post for screenshots from Medium/Youtube.

You’ll have to make the call for yourself if this is of benefit to use your users or not.

Pros

  1. Semantically accurate.
  2. No math or complex computation involved.
  3. Minimum markup required.
  4. Seamless fallback for browsers with no support for HTML5 progress element.
  5. Seamless fallback for browsers with JavaScript disabled.

Cons

  1. Cross-browser styling is complex.
  2. Fallback for older browsers relies on traditional div/span(s) technique making the entire code bloat.
  3. Scroll hijacking can potentially reduce FPS on webpages with complex DOM structure and intricate styles.
  4. It conflicts with the progress bar used to indicate web page loading and might confuse users.

Reading Position Indicator originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/reading-position-indicator/feed/ 28 169448
The HTML5 meter Element https://css-tricks.com/html5-meter-element/ https://css-tricks.com/html5-meter-element/#comments Tue, 26 Nov 2013 21:45:27 +0000 http://css-tricks.com/?p=157146 The following is a guest post by Pankaj Parashar. Pankaj has written here before, last time about the progress element. Fitting indeed then to write again about the very related meter element. They are different both functionally and


The HTML5 meter Element originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
The following is a guest post by Pankaj Parashar. Pankaj has written here before, last time about the progress element. Fitting indeed then to write again about the very related meter element. They are different both functionally and semantically, so read on!

As defined by W3C,

The meter element represents a scalar measurement within a known range, or a fractional value; for example disk usage, the relevance of a query result, or the fraction of a voting population to have selected a particular candidate. This is also known as a gauge.

If you are like me, the above spec wouldn’t make much sense until we dive deep into the implementation. So let’s just start with a basic markup for the meter element:

<meter></meter>

Similar to its sibling – the progress element – a meter element must also have both a start tag (<meter>) and an end tag (</meter>). This becomes very useful when we devise a robust fallback technique for older browsers that do not support the meter element, later in this article.

Content model

The meter element uses the phrasing content model which means it can contain the text of the document, along with the elements that mark up that text, but there must be no (additional) meter element among its descendants.

Attributes

Apart from the global attributes, the meter element can have 6 more attributes.

value – A floating point number that represents the current value of the measured range. This must be between the min and the max value (if specified).

min – Indicates the lower bound of the measured range. This must be less than the max value (if specified). If unspecified, the minimum value is 0.

max – Indicates the upper bound of the measured range. This must be greater than the min value (if specified). If unspecified, the maximum value is 1.0.

low – It represents the upper bound of the low end of the measured range. This must be greater than the min attribute, but less than the high and max value (if specified). If unspecified, or if less than the minimum value, the low value is equal to the min value.

high – It represents the lower bound of the high end of the measured range. This must be less than the max attribute, but greater than the low and min value (if specified). If unspecified, or if greater than the max value, the high value is equal to the max value.

optimum – This attribute indicates the optimum value and must be within the range of min and max values. When used with the low and high attribute, it indicates the preferred zone for a given range. For example:

  • min ≤ optimum ≤ low – If the optimum value is between the min and the low values, then the lower range is considered to be the preferred zone.
  • high ≤ optimum ≤ max – If the optimum value is between the high and the max values, then the upper range is considered to be the preferred zone.

A meter with everything would look like:

<meter min="0" low="10" optimum="50" high="90" max="100"></meter>

Rules of thumb

  1. All the above mentioned attributes may be floating point numbers e.g. 12, -8, 3.625
  2. Based on the definition of each attribute the following inequalities become true,
    • min ≤ value ≤ max
    • min ≤ low ≤ high ≤ max (if low/high specified)
    • min ≤ optimum ≤ max (if optimum specified)
  3. There is no explicit way to specify units in the meter element, but the authors are encouraged to specify the units using the title attribute. For example, <meter max="256" value="120" title="GB">120GB out of 256GB are used</meter>

Do not use meter element to…

  1. indicate the progress completion of a task (use progress element instead).
  2. represent a scalar value of an arbitrary range — for example, to report a weight, or height of a person.

Experiment #1 – Different states of meter element

This experiment shows the various states of the meter element under different combination of input values for each attribute. Feel free to edit the attribute values of the main code to tweak the meter gauge ouput.

See the Pen HTML5 Meter Element by Pankaj Parashar (@pankajparashar) on CodePen

Experiment #2 – OSX style disk usage

In this experiment, we’ll try and simulate the appearance of the disk usage panel in OS X using the meter element and then style it as cross-browser as possible.

Populating internal attributes of our meter tag with the known set of input values.

  • Total size of the disk – 120.47GB (our max attribute)
  • Current disk usage – 55.93GB (our value attribute)
  • Minimum disk size – 0 (our min attribute, not required as the default value is 0)
  • Unit – GB (our title attribute that specifies the unit)
<meter max="120" value="55.93" title="GB"></meter>

Before we apply any CSS, the meter gauge looks like this in Chrome 30 on OX X 10.9.

Default appearance of the meter element in Chrome 30 on OS X 10.9
Although the spec recommends including a textual representation of the gauge’s state inside the meter tag for older browsers, we’ll keep it blank for now to add the fallback content later in this article to support them.

This is pretty much all that we can do in HTML as rest of the work is done by CSS. At this stage let’s not worry about the fallback techniques for supporting older browsers that don’t understand the meter element.

Styling the meter element

Just like any other element, we can define dimensions by specifying width and height for meter.

meter {
  width: 500px;
  height: 25px;
}

This is where things become interesting because generally most of the A-grade browsers provide separate pseudo classes to style the meter element. Although Opera moving to Blink leaves us with less browsers to care for. At this stage, we don’t really have to know about which versions of each browser support the meter element, because our fallback technique will take care of the rest. We classify them as follows:

  • Webkit/Blink
  • Firefox
  • Internet Explorer

1. Webkit/Blink (Chrome/Safari/Opera/iOS)

On inspecting the meter element via Chrome Developer Tools, we can reverse-engineer the implementation of the spec in webkit browsers.

Inspect element on Chrome DevTools

In addition, the User-Agent stylesheet of WebKit provides a wealth of information on how you can use various pseudo classes to access different states of the meter element.

User-Agent stylesheet of WebKit browsers
Pseudo class Description
-webkit-meter-inner-element Additional markup to render the meter element as read-only.
-webkit-meter-bar Container of the meter gauge that holds the value.
-webkit-meter-optimum-value The current value of the meter element and is by default green when the value attribute falls inside the low-high range.
-webkit-meter-suboptimum-value Gives a yellow color to the meter element when the value attribute falls outside the low-high range.
-webkit-meter-even-less-good-value Gives a red color to the meter element when the value and the optimum attributes fall outside the low-high range but in opposite zones. For example, value < low < high < optimum or value > high > low > optimum

First, let’s start by resetting the default appearance of the meter gauge by using -webkit-appearence: none;

meter {
  width: 500px;
  height: 25px;
  -webkit-appearance: none; /* Reset appearance */
  border: 1px solid #ccc;
  border-radius: 3px;
}

For this experiment, we will only be using -webkit-meter-bar (to style the container) and -webkit-meter-optimum-value (to style the value) pseudo classes. Each color in background linear gradient represents the space consumed by the sub-categories like – Apps, Movies, Photos etc.

WebKit pseudo classes on the meter element
meter::-webkit-meter-bar {
  background: none; /* Required to get rid of the default background property */
  background-color: whiteSmoke;
  box-shadow: 0 5px 5px -5px #333 inset;
}
Output after styling the background container
meter::-webkit-meter-optimum-value {
  box-shadow: 0 5px 5px -5px #999 inset;
  background-image: linear-gradient(
    90deg, 
    #8bcf69 5%, 
    #e6d450 5%,
    #e6d450 15%,
    #f28f68 15%,
    #f28f68 55%,
    #cf82bf 55%,
    #cf82bf 95%,
    #719fd1 95%,
    #719fd1 100%
  );
  background-size: 100% 100%;
}
Output after styling the meter gauge value

CSS3 Transition/Animation

WebKit browsers support both transition and animation on the meter element. Just for the sake of testing, I experimented by changing the width of the value(on :hover) using transitions and animating the background position of the container using keyframes. Although not applicable for practical usage, both the experiments turned out pretty well on all three browsers.

meter::-webkit-meter-optimum-value {
  -webkit-transition: width .5s;
}

meter::-webkit-meter-optimum-value:hover {
  /* Reset each sub-category to 20% */
  background-image: linear-gradient(
    90deg, 
    #8bcf69 20%, 
    #e6d450 20%,
    #e6d450 40%,
    #f28f68 40%,
    #f28f68 60%,
    #cf82bf 60%,
    #cf82bf 80%,
    #719fd1 80%,
    #719fd1 100%
  );
  transition: width .5s;
  width: 100% !important; /* !important required. to override the inline styles in WebKit. */
}
CSS3 Transitions in action (:hover)
meter::-webkit-meter-bar {
  /* Let's animate this */
  animation: animate-stripes 5s linear infinite;
  background-image:
    linear-gradient(
      135deg,
      transparent,
      transparent 33%,
      rgba(0, 0, 0, 0.1) 33%,
      rgba(0, 0, 0, 0.1) 66%,
      transparent 66%
    );
  background-size: 50px 25px;
}

@keyframes animate-stripes {
  to { background-position: -50px 0; }
}
CSS3 Animated background stripes in action

Pseudo Elements

At the time of writing, only webkit browsers support pseudo elements on meter gauge. For this experiment, we could use the pseudo elements to display the meta information like HD Name, Free space right above the meter gauge.

meter {
  margin: 5em;
  position: relative;
}

meter::before {
  content: 'Macintosh HD';
  position: absolute;
  top: -100%;
}

meter::after {
  content: '64.54 GB free out of 120.47 GB';
  position: absolute;
  top: -100%;
  right: 0;
}
Pseudo elements in action

Apart from WebKit, the support for pseudo elements in other browsers is non-existent. Hence, there is no good reason to embed content inside pseudo elements, at least for now.

2. Firefox

Firebug screenshot (on inspecting the meter element in Firefox 25)

Similar to WebKit, Firefox uses -moz-appearence: meterbar to paint the meter gauge. We can get rid of the default bevel and emboss by resetting it to none.

meter {
  /* Reset the default appearence */
  -moz-appearance: none;
  width: 550px;
  height: 25px;
}

Firefox is shipped with a wholesome list of pseudo classes to style different states of the meter gauge. The snapshot below has been grabbed from the forms.css file that can be accessed from inside Firebug.

[forms.css] snapshot for meter element in Firefox 25
Pseudo class Description
-moz-meter-bar Represents the current value of the meter gauge that can be used to style the properties of the meter gauge value.
-moz-meter-optimum It gives a green color to the meter element when the value attribute falls inside the low-high range.
-moz-meter-sub-optimum It gives a yellow color to the meter element when the value attribute falls outside the low-high range.
-moz-meter-sub-sub-optimum It gives a red color to the meter element when the value and the optimum attributes fall outside the low-high range but in opposite zones. For example, value < low < high < optimum or value > high > low > optimum

For this experiment, we will only use ::-moz-meter-bar to style the background of the meter gauge value.

meter::-moz-meter-bar {
  box-shadow: 0 5px 5px -5px #999 inset;
  background-image: linear-gradient(
    90deg, 
    #8bcf69 5%, 
    #e6d450 5%,
    #e6d450 15%,
    #f28f68 15%,
    #f28f68 55%,
    #cf82bf 55%,
    #cf82bf 95%,
    #719fd1 95%,
    #719fd1 100%
  );
  background-size: 100% 100%;
}

Interestingly, in Firefox you can style the background of the container using the meter selector itself.

meter {
  /* Firefox */
  background: none; /* Required to get rid of the default background property */
  background-color: whiteSmoke;  
  box-shadow: 0 5px 5px -5px #333 inset;
}

Firefox doesn’t support ::before and ::after pseudo elements on the meter gauge. The support for CSS3 transitions and animation is a bit shaky as well. Hence, there is no good reason to use them until the behavior becomes consistent across browsers.

3. Internet Explorer

To my knowledge, no stable version of Internet Explorer supports the meter element. Even the Modernizr test suite failed to detect meter in IE11 preview on Windows 8.1. This perhaps leaves us only with the fallback approaches that will be discussed in the next section.

if ('value' in document.createElement('meter')) {
  alert("Meter element is supported");
} else {
  alert("Meter element Not supported");
}

What about browsers that don’t support meter?

Can I Use (and simple testing) reports the meter gauge is natively supported in: Firefox 16+, Opera 11+, Chrome, Safari 6+. Internet Explorer, however offers no support what-so-ever for any version. If you want to make meter element work in older browsers, then you’ve got two options:

1. Polyfill

Randy Peterman has written a polyfill that makes meter element work in older browsers (especially IE). During my cross-browser testing, I found that the polyfill works for all IE browsers down to IE6! which makes it a good candidate for usage in production.

2. HTML fallback

This is my preferred (no-JS) approach. It makes use of a common technique that was also discussed in my previous article for CSS-Tricks on the HTML5 progress element.

<meter value="55.93" min="0" max="120.47" title="GB">
  <div class="meter-gauge">
    <span style="width: 46.42%;">Disk Usage - 55.93GB out of 120GB</span>
  </div>
</meter>

The idea is to simulate the appearance of a meter gauge using div and span inside the meter tag. Modern browsers that support meter will ignore the content within the tag. Older browsers that cannot render the meter element will ignore the tag and render the markup inside it.

.meter-gauge {
    border: 1px solid #ccc;
    border-radius: 3px;
    background-color: whiteSmoke;
    box-shadow: 0 5px 5px -5px #333 inset;
    width: 550px;
    height: 25px;
    display: block;
}

.meter-gauge > span {
  height: inherit;  
  box-shadow: 0 5px 5px -5px #999 inset;
  background-color: blue;
  background-image: linear-gradient(
    90deg, 
    #8bcf69 5%, 
    #e6d450 5%,
    #e6d450 15%,
    #f28f68 15%,
    #f28f68 55%,
    #cf82bf 55%,
    #cf82bf 95%,
    #719fd1 95%,
    #719fd1 100%
  );
  background-size: 100% 100%;
  display: block;
  text-indent: -9999px;
}

It is fairly common to use both the techniques in conjunction and it is perfectly safe for usage in production sites. The demo should run fine for all the browsers including Internet Explorer (down to IE6!). The meter gauge color is blue in all the versions of Internet Explorer. Opera 11 and 12 doesn’t permit changing the color of the meter gauge. Hence, it shows the default green color. The demo also uses some additional markup to display the disk usage of each sub-category like Apps, Movies, Photos etc.

See the Pen OSX-style Disk Usage by Pankaj Parashar (@pankajparashar) on CodePen

More Resources


The HTML5 meter Element originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/html5-meter-element/feed/ 26 157146
The HTML5 progress Element https://css-tricks.com/html5-progress-element/ https://css-tricks.com/html5-progress-element/#comments Wed, 28 Aug 2013 19:09:50 +0000 http://css-tricks.com/?p=148360 The following is a guest post by Pankaj Parashar. Pankaj wrote to me about some pretty cool styled progress elements he created. I asked if he’d be interested in fleshing out the idea into an article about styling them


The HTML5 progress Element originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
The following is a guest post by Pankaj Parashar. Pankaj wrote to me about some pretty cool styled progress elements he created. I asked if he’d be interested in fleshing out the idea into an article about styling them in general. Thankfully, he obliged with this great article about using them in HTML, styling them with CSS as best as you can cross-browser, and fallbacks.

Here is the basic markup for the progress element:

<progress></progress>

As per the standard defined by W3C, the progress element represents the completion progress of a task. A progress element must have both a start tag (i.e. <progress>) and an end tag (i.e. </progress>), even though it looks like a replaced element (like an input). This is good though, as it helps with fallback content as we’ll cover later.

Apart from the global attributes, it can have two more attributes:

  • max – Indicates how much task needs to be done before it can be considered as complete. If not specified the default value is 1.0.
  • value – Indicates the current status of the progress bar. It must be greater than or equal to 0.0 and less than or equal to 1.0 or the value of the max attribute (if present).

States of progress bar

A progress bar can be in two states – indeterminate and determinate.

1. Indeterminate

Indeterminate state of the progress bar in Chrome 29 on Mac OS 10.8

Based on your combination of browser and operating system, the progress bar can look different. Zoltan “Du Lac” Hawryluk covers the cross browser behavior of progress element in great depth in his article on HTML5 progress bars (which is definitely worth reading). Wufoo has some screenshots of how it looks on other operating systems on their support page for progress.

It’s pretty easy to target and style an indeterminate progress bar because we know that it doesn’t contain the value attribute. We can make use of CSS negation clause :not() to style it:

progress:not([value]) {
   /* Styling here */
}

2. Determinate

Throughout this article, we’ll only focus on styling the determinate state of the progress bar. So let’s change the state by adding the max and value attribute.

<progress max="100" value="80"></progress>

Without applying any CSS, the progress bar looks like this in Chrome 29 on Mac OS 10.8.

Determinate state of the progress bar in Chrome 29 on Mac OS 10.8
Note that only adding the max attribute doesn’t change the state of the progress bar because the browser still doesn’t know what value to represent.

This is pretty much all that we can do in HTML as rest of the work is done by CSS. At this stage let’s not worry about the fallback techniques for supporting older browsers that don’t understand the progress element.

Styling progress bars

We can target determinate progress bars using the progress[value] selector. Using progress only is fine as long as you know that you do not have any indeterminate progress bars in your markup. I tend to use the former because it provides clear distinction between the two states. Just like any other element we can add dimensions to our progress bar using width and height:

progress[value] {
  width: 250px;
  height: 20px;
}

This is where the fun part ends and things get complicated because each category of browsers provide separate pseudo classes to style the progress bar. To simplify things, we don’t really care about which versions of each browser support the progress element, because our fallback technique will take care of the rest. We classify them as follows:

  • WebKit/Blink Browsers
  • Firefox
  • Internet Explorer

WebKit/Blink (Chrome/Safari/Opera)

Google Chrome, Apple Safari and the latest version of Opera (16+) falls into this category. It is evident from the user agent stylesheet of webkit browsers, that they rely on -webkit-appearance: progress-bar to style the appearance of progress element.

User-agent stylesheet of webkit browsers

To reset the default styles, we simply set -webkit-appearance to none.

progress[value] {
  /* Reset the default appearance */
  -webkit-appearance: none;
   appearance: none;

  width: 250px;
  height: 20px;
}
Progress bar appearance after reset

On further inspecting the progress element in Chrome Developer Tools, we can see how the spec is implemented.

Chrome Developer Tools Snapshot

WebKit/Blink provides two pseudo classes to style the progress element:

  • -webkit-progress-bar is the pseudo class that can be used to style the progress element container. In this demo we’ll change the background color, border-radius and then apply inset box shadow to the progress element container.
  • -webkit-progress-value is the pseudo class to style the value inside the progress bar. The background-color of this element by default is green which can be verified by inspecting the user-agent stylesheet. For this demo we will create a candystrip effect using linear-gradient on background-image property.

First we’ll style the -webkit-progress-bar (the container):

progress[value]::-webkit-progress-bar {
  background-color: #eee;
  border-radius: 2px;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.25) inset;
}
Styling progress bar container

Next we’ll style the -webkit-progress-value (the bar) with multiple gradient backgrounds. One for striping, one for top to bottom shadowing, and one for left to right color variation. We’ll use the -webkit- prefix for the gradients since we’re using it for the progress bar itself anyway.

progress[value]::-webkit-progress-value {
  background-image:
	   -webkit-linear-gradient(-45deg, 
	                           transparent 33%, rgba(0, 0, 0, .1) 33%, 
	                           rgba(0,0, 0, .1) 66%, transparent 66%),
	   -webkit-linear-gradient(top, 
	                           rgba(255, 255, 255, .25), 
	                           rgba(0, 0, 0, .25)),
	   -webkit-linear-gradient(left, #09c, #f44);

    border-radius: 2px; 
    background-size: 35px 20px, 100% 100%, 100% 100%;
}
Styling progress bar value

Adding Animation

At the time of writing only WebKit/Blink browsers support animations on progress element. We’ll animate the stripes on -webkit-progress-value by changing the background position.

@-webkit-keyframes animate-stripes {
   100% { background-position: -100px 0px; }
}

@keyframes animate-stripes {
   100% { background-position: -100px 0px; }
}

And use this animation on the -webkit-progress-value selector itself.

-webkit-animation: animate-stripes 5s linear infinite;
        animation: animate-stripes 5s linear infinite;

Update: Animations don’t seem to work anymore on the pseudo elements within a progress element, in Blink. At the time this was published, they did. Brian LePore reported this to me and pointed me toward this thread discussing it and created a reduced test case. Simuari’s thought:

Maybe it’s the same with the <progress> that @keyframes defined outside of the Shadow DOM can’t get accessed by an element inside. From the timing it might have changed in Chromium 39/40?

and also:

what seems to work is moving the animation from progress::-webkit-progress-bar to progress

Bardi Harborow reports that’s not true, though, and also pointed out the logged bug.

Pseudo Elements

At the time of writing only WebKit/Blink browsers support pseudo elements ::before and ::after on progress bar. By simply looking at the progress bar, it is not possible to tell the actual value. We can solve this problem by displaying the actual value right at the tail-end of the progress bar using either ::before or ::after.

progress[value]::-webkit-progress-value::before {
  content: '80%';
  position: absolute;
  right: 0;
  top: -125%;
}
Pseudo elements in action

Interestingly, content: attr(value) doesn’t work on progress bars. However, if you explicitly specify the text inside the content attribute, it works! I haven’t been able to find out the reason behind this behavior. Since this works only on WebKit/Blink browsers, there is no good reason to embed content inside pseudo elements, at least for now.

Update 11/2016: progress::after { content: attr(value); } does seem to work in Blink now, but nothing else.

Similarly, ::after is used to create nice little hinge effect at the end of the progress bar. These techniques are experimental and not really recommended to be used if you are aiming for cross-browser consistency.

progress[value]::-webkit-progress-value::after {
  content: '';
  width: 6px;
  height: 6px;
  position: absolute;
  border-radius: 100%;
  right: 7px;
  top: 7px;
  background-color: white;
}

2. Firefox

Similar to WebKit/Blink, Firefox also uses -moz-appearence: progressbar to paint the progress element.

Firebug screenshot

By using appearence: none we can get rid of the default bevel and emboss. This unfortunately leaves behind a faint border in Firefox which can be removed by using border: none. This also solves the border issue with Opera 12.

progress[value] {
  /* Reset the default appearance */
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  
  /* Get rid of default border in Firefox. */
  border: none;
  
  /* Dimensions */
  width: 250px;
  height: 20px;
}
Faint border in Firefox and Opera

Firefox provides a single pseudo class (-moz-progress-bar) we can use to target the progress bar value. This means that we cannot style the background of the container in Firefox.

progress[value]::-moz-progress-bar { 
  background-image:
    -moz-linear-gradient(
      135deg, 
      transparent 33%, 
      rgba(0, 0, 0, 0.1) 33%, 
      rgba(0, 0, 0, 0.1) 66%, 
      transparent 66% 
    ),
    -moz-linear-gradient(
      top, 
      rgba(255, 255, 255, 0.25), 
      rgba(0, 0, 0, 0.25)
    ),
    -moz-linear-gradient(
      left, 
      #09c, 
      #f44
    );

  border-radius: 2px; 
  background-size: 35px 20px, 100% 100%, 100% 100%; 
}

Firefox doesn’t support ::before or ::after pseudo classes on progress bar, nor does it allow CSS3 keyframe animation on progress bar, which gives us a slightly reduced experience.

3. Internet Explorer

Only IE 10+ natively supports progress bar, and only partially. It only allows changing the color of the progress bar value. IE implements value of the progress bar as the color attribute rather than the background-color.

progress[value]  {
  /* Reset the default appearance */
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;

  /* Get rid of default border in Firefox. */
  border: none;

  /* Dimensions */
  width: 250px;
  height: 20px;

  /* For IE10 */
  color: blue; 
}

What about browsers that don’t support them?

The progress element is natively supported in: Firefox 16+, Opera 11+, Chrome, Safari 6+. IE10+ is partially supports them. If you want to support older browsers, you’ve got two options.

1. Lea Verou’s HTML5 progress polyfill

Lea Verou’s excellent polyfill adds almost full support for Firefox 3.5-5, Opera 10.5-10.63, IE9-10. This also adds partial support in IE8. It involves including progress-polyfill.js file in your HTML and adding CSS selectors that the script file uses. To know more about its usage, check out the CSS source code of the project page.

2. HTML fallback

This is my preferred (no-js) approach. It makes use of a common technique that is also used by audio and video elements.

<progress max="100" value="80">
    <div class="progress-bar">
        <span style="width: 80%;">Progress: 80%</span>
    </div>
</progress>

Simulate the look and feel of progress bar using div and span inside the progress tag. Modern browsers will ignore the content inside the progress tag. Older browsers that cannot identify progress element will ignore the tag and render the markup inside it.

.progress-bar {
  background-color: whiteSmoke;
  border-radius: 2px;
  box-shadow: 0 2px 3px rgba(0, 0, 0, 0.25) inset;

  width: 250px;
  height: 20px;
  
  position: relative;
  display: block;
}
  
.progress-bar > span {
  background-color: blue;
  border-radius: 2px;

  display: block;
  text-indent: -9999px;
}

It is fairly common to use both the techniques in conjunction and it is perfectly safe for production sites. Once you get hold of styling a single progress bar, then adding styles for multiple progress bars is merely an exercise which can be accomplished using classes.

See the Pen Skillset using HTML5 progress bars with CSS3 animations by CSS-Tricks (@css-tricks) on CodePen.

The demo should run fine in all browsers, including Internet Explorer (down to IE 8). The progress bar color is blue in all the versions of Internet Explorer. Opera 11 and 12 doesn’t permit changing the progress bar color. Hence, it shows the default green color. The demo uses additional markup to display some meta information about the progress bar and the percentage value.

For additional reading, check out the HTML5 Doctor article. It covers some similar ground but has some bits about a few additional attributes as well as how to update the bar with JavaScript if you need that.


The HTML5 progress Element originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/html5-progress-element/feed/ 42 148360