Front End Web Development

Designing calculator apps

Css Tricks - Thu, 04/01/2021 - 4:23am

It is extremely weird that the calculator apps, even the default ones baked into desktop operating systems, embrace the UI and UX of those little cheap-o plastic physical calculators. I like what Florens Verschelde’s Math teacher had to say:

I had a Math teacher who would ban pocket calculators that didn’t show both your input and its result at the same time. If a calculator couldn’t show this:

38 ÷ 1.2 = 31.666666666667

You couldn’t use it.

The article ends up being in praise of Soulver, which I agree is a nice modern take on the idea of calculations.

I wish it was on Setapp, as I’d probably use it. But I don’t do enough regular mathin’ to go for the full price (today, anyway).

Direct Link to ArticlePermalink

The post Designing calculator apps appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Where the World Wide Web Shines

Css Tricks - Wed, 03/31/2021 - 12:17pm

Here’s a fabulous post by Vitaly Friedman that looks at how to make accessible front-end components and what problems there are today when it comes to building them.

There’s so much great info packed into this one post that I’m going to keep it open in a tab for quite some time. But I have two thoughts here. First, just skimming through the article is enough to make anyone realize that accessibility is a complex subject and it’s so very easy to get things wrong; colors, images, text, HTML, mouse pointer vs. touch, small screens vs. large screens, charts and data viz, form components, layout and semantic ordering. The list goes on and on. It’s clear to me now (and I am late to the party) that accessibility is a full-time job.

Second, Vitaly makes note of some of the excellent work that the Government Digital Service (GDS) is doing in the UK by releasing open-source components such as accessible-autocomplete. And I have to say, I think the work that GDS is doing is so very inspiring to me as a web designer and developer.

Here’s a story: a few years ago I had to book an appointment to get a driver’s license. I hopped on the website and, immediately, I recognized that it was using the GDS design system. That gave me a great sigh of relief, but then I found myself sailing through this form at lightning speed. By the end, I realized that this is what every website should feel like; I used the site, did what I needed to do as quickly as possible, and then left.

It was one of the most shocking experiences for me as a web designer because there was no cruft, no junk, and no messing around with the experience in any way. It was fast, didn’t break down, crash the browser, or confuse me at all. The form inputs were big and clickable and the correct keyboard was selected when I viewed it on my phone. All of this accessibility work that they’ve poured into making things just work is a joyous thing.

This reminds me of something that Jeremy Keith wrote about the other day when he used another government website to get vaccinated:

[…] it’s a sequence of short forms, clearly labelled. Semantic accessible HTML, some CSS, and nothing more. If your browser doesn’t support JavaScript (or you’ve disabled it for privacy reasons), that won’t make any difference to your experience. This is the design system in action and it’s an absolute pleasure to experience.

[…] Maybe I’ll never need to visit that URL again. In the case of the NHS, I hope I won’t need to visit again. I just need to get in, accomplish my task, and get out again. This is where the World Wide Web shines.

Direct Link to ArticlePermalink

The post Where the World Wide Web Shines appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Overlay Fact Sheet

Css Tricks - Wed, 03/31/2021 - 10:36am

I would hope all our web designer/developer spidey senses trigger when the solution to an accessibility problem isn’t “fix the issue” but rather “add extra stuff to the page.” This Overlay Fact Sheet website explains that. An “Overlay” is one of those “add extra stuff to the page” things, ostensibly for improving accessibility. Except, even though marketing may suggest they are a silver bullet to accessibility, they are… not.

The site does a much better job laying that out than I can, so go check it out. As I write, it’s signed by 352 people, mostly people who are accessibility professionals.

Direct Link to ArticlePermalink

The post Overlay Fact Sheet appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

This Web Site is a Tech Talk

Css Tricks - Wed, 03/31/2021 - 4:27am

This literal tech talk (YouTube video embedded in there) by Zach Leatherman is a good time. The talk is sprinkled with fun trickery, so I’m just taking notes on some on it here:

  • I have no idea how he pulled off the “bang on the keyboard and get perfect code” thing, but it reminds me of Jake Albaugh’s “Self-Coding” Pens.
  • Adding contenteditable on the <body> makes the whole page editable! Did you know document.designMode = "on" does the same thing in JavaScript? (More on making DevTools a design tool.)
  • There’s a short bit where the typing happens in two elements at once. CodePen supports that! Just CMD + click into the editor where you want another one to be, or make it part of a snippet.
  • System fonts are nice. I like how easy they are to invoke with system-ui. Firefox doesn’t seem to support that, so I guess we need the whole stack. I wonder how close we are to just needing that one value. Iain Bean has more on this in his “System fonts don’t have to be ugly” post.
  • box-decoration-break is a nice little touch for “inline headers.” The use of @supports here makes great sense as it’s not just that one property in use, but several. So, in a non-support situation, you’d want to apply none of it.
  • Slapping a <progress> in some <li> elements to compare rendering strategies is a neat way to get some perfect UI without even a line of CSS.
  • Making 11ty do syntax highlighting during the build process is very cool. I still use Prism.js on this site, which does a great job, but I do it client-side. I really like how this 11ty plugin is still Prism under the hood, but just happens when the page is built. I’d love to get this working here on this WordPress site, which I bet is possible since our code block in the block editor is a custom JavaScript build anyway.
  • In the first bullet point, I wrote that I had no idea how Zach did the “bang on the keyboard and get perfect code” but if you watch the bit about syntax highlighting and keep going, Zach shows it off and it’s a little mind spinning.

I think Zach’s overall point is strong: we should question any Single-Page-App-By-Default website building strategy.

As a spoonful of baby bear porridge here, I’d say I’m a fan of both static site generators and JavaScript frameworks. JavaScript frameworks offer some things that are flat-out good ideas for building digital products: components and state. Sometimes that means that client-side rendering is actually helpful for the interactivity and overall feel of the site, but it’s unfortunate when client-side rendering comes along for the ride by default instead of as a considered choice.

Direct Link to ArticlePermalink

The post This Web Site is a Tech Talk appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

ShopTalk Patreon

Css Tricks - Tue, 03/30/2021 - 10:29am

Dave and I launched a Patreon for ShopTalk Show. You get two completely priceless things for backing us:

  1. That great feeling you’re supporting the show, which has costs like editing, transcribing, developing, and hosting.
  2. Access to our backer-only Discord.

I think the Discord might be my favorite thing we ever done. Sorry if I’m stoking the FOMO there, but just saying, it’s a good gang. My personal intention is to be helpful in there, but everyone else is so helpful themselves that I’ve actually learned more than I’ve shared.

The Patreon is Here

Direct Link to ArticlePermalink

The post ShopTalk Patreon appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

You want margin-inline-start

Css Tricks - Tue, 03/30/2021 - 4:24am

David Bushell in ”Changing CSS for Good“:

I’m dropping “left“ and “right“ from my lexicon. The new CSS normal is all about Logical Properties and Values […] It can be as easy as replacing left/right with inline start/end. Top/bottom with block start/end. Normal inline flow, Flexbox, and Grid layouts reverse themselves automatically.

I figured it made sense as a “You want…” style post. Geoff has been documenting these properties nicely in the Almanac.

The post You want margin-inline-start appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Deliver Enhanced Media Experiences With Google’s Core Web Vitals

Css Tricks - Tue, 03/30/2021 - 4:23am

Hello! Satarupa Chatterjee from Cloudinary. There is a big change coming from Google in May 2021 having to do with their Core Web Vitals (CWVs). It’s worth paying attention here, as this is going to be a SEO factor.

I recently spoke with Tamas Piros about CWVs. The May 2021 update will factor in CWVs, along with other factors like mobile-friendliness and safe browsing, to generate a set of benchmarks for search rankings. Doubtless, the CWVs will directly affect traffic for websites and apps alike. Tamas is a developer-experience engineer at Cloudinary, a media-optimization expert, and a Google developer-expert in web technologies and web performance.

Here’s a written version of the video above, where the questions (Qs) are me, Satarupa, asking and Tamas answering (As).

Q: How did Google arrive at the three Core Web Vitals and their values?

A: As a dominant force in the search space, Google has researched in depth what constitutes a superb user experience, arriving at three important factors, which the company calls, collectively, the Core Web Vitals.

Before explaining them, I’d like to recommend an informative article, published last May on the Chromium Blog, titled The Science Behind Web Vitals. At the bottom of the piece are links to papers on the research that led to the guidelines for accurately evaluating user experiences.

Now back to the three Core Web Vitals. The first one affects page-load speed, which Google calls Largest Contentful Paint (LCP) with a recommendation of 2.5 seconds or less for the largest element on a page to load.

The second metric is First Input Delay (FID), which is a delta between a user trying to interact with a page, and the browser effectively executing that action. Google recommends 100 milliseconds or less. 

The third and last metric is Cumulative Layout Shift (CLS), which measures how stable a site is while it’s loading or while you’re interacting with it. In other words it is a measurement of individual layout shifts as well as unexpected layout shifts that happen during the lifespan of a page. The calculation involves impact and distance fractions which are multiplied together to give a final value. Google advocates this value to be 0.1 or less.

Q: How do the Core Web Vitals affect e-commerce?

A: Behind the ranking of Google search results are many factors, such as whether you use HTTPS and how you structure your content. Let’s not forget that relevant and well-presented content is as important as excellent page performance. The difference that core web vitals will make cannot be overstated. Google returns multiple suggestions for every search, however remember that good relevance is going to take priority. In other words good page experience will not override having great relevant content For example, if you search for Cloudinary, Google will likely show the Cloudinary site at the top of the results page. Page experience will become relevant when there are multiple available results, for a more generic search such as ‘best sports car’. In this case Google establishes that ranking based on the page’s user experience, too, which is determined by the Core Web Vitals.

Q: What about the other web vitals, such as the Lighthouse metrics? Do they still matter?

A: Businesses should focus primarily on meeting or staying below the threshold of the Core Web Vitals. However, they must also keep in mind that their page load times could be affected by other metrics, such as the length of time the first purchase takes and the first contentful paint.

For example, to find out what contributes to a bad First Input Delay—the FID, check the total blocking time and time to interact. Those are also vitals, just not part of the Core Web Vitals. You can also customize metrics with the many robust APIs from Google.. Such metrics could prove to be invaluable in helping you identify and resolve performance issues.

Q: Let’s talk about the Largest Contentful Paint metric, called LCP. Typically, the heaviest element on a webpage or in an app is an image. How would you reduce LCP and keep it below the Google threshold of 2.5 seconds?

A: What’s important to remember with regards to LCP is that we are talking about the largest piece of content that gets loaded on a page, and that is visible in the viewport (that is, it’s visible above the fold). Due to popular UX design patterns it’s likely that the largest, visible element is a hero image.

Google watches for <img> elements as well as <image> elements inside an SVG element. Video elements are considered too but only if they contain a poster attribute. Also of importance to Google are block-level elements, such as text-related ones like <h1>, <h2>, etc., and <span>.

All that means that you must load the largest piece of content as fast as possible. If your LCP is a hero image, be sure to optimize it—but without degrading the visual effects. Check out Cloudinary’s myriad effective and intuitive options for optimization. If you can strike a good balance between the file size and the visual fidelity of your image, your LCP will shine. 

Q: Suppose it’s now May 2021. What’s the likely effect of Google’s new criteria for search rankings for an e-commerce business that has surpassed the thresholds of all three or a couple of the Core Web Vitals?

A: According to Google, sites that meet the thresholds of the Core Web Vitals enjoy a 24-percent lower abandonment rate. The more you adhere to Google’s guidelines, the more engaging your site or app becomes and the faster your sales will grow. Needless to say, an appealing user experience attracts visitors and retains them, winning you an edge over the competition. Of course bear in mind the other search optimization guidelines set out by Google.

Again, be sure to optimize images, especially the most sizable one in the viewport, so that they load as fast as possible.

Q:  It sounds like e-commerce businesses should immediately start exploring ways to meet or better the vitals’ limits. Before we wrap up, what does the future look like for Core Web Vitals?

A: Late last year, Google held a conference and there were multiple talks touching upon this exact subject. All major changes will go into effect on a per-year basis, and Google has committed to announcing them well in advance.

Behind the scenes, Google is constantly collecting data from the field and checking them against user expectations. The first contentful paint, which I mentioned before, is under consideration as another Core Web Vital. Also, Google is thinking about reducing the yardstick for the First Input Delay metric—the FID, remember?—from 100 milliseconds to 75 or even 50.

Beyond that, Google has received a lot of feedback about some of the Core Web Vitals not working well for single-page apps. That’s because those apps are loaded only once. Even if they score an ideal Cumulative Layout Shift—that’s CLS—as you click around the page, things might move around and bring down the score. Down the road, Google might modify CLS to better accommodate single-page apps. 

Also on Google’s radar screen are metrics for security, privacy, and accessibility. Google promises to fine-tune the current metrics and launch new ones more frequently than major releases, including the introduction of new Core Web Vital metrics. 

So, change is the only constant here. I see a bright future for the vitals and have no doubt that we’re in good hands. Remember that Google vigilantly collects real user data as analytics to help figure out the appropriate standards. As long as you keep up with the developments and ensure that your site or app comply with the rules, you’ll get all greens throughout the scoreboard. That’s a great spot to be in.

Cloudinary offers myriad resources on media experience (MX), notably the MX Matters podcast, which encompasses experts’ take on the trends in today’s visual economy along with bulletins on new products and enhancements. Do check them out.

The post Deliver Enhanced Media Experiences With Google’s Core Web Vitals appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

:where() has a cool specificity trick, too.

Css Tricks - Mon, 03/29/2021 - 2:09pm

There is a lot of hype on the :is() pseudo-selector lately, probably because now that Safari 14 has it, it’s supported across all the major browsers. You’ve got Miriam tweeting about it, Kevin Powell doing a video, Šime getting it into the Web Platform News, and Robin mentioning it. Bramus really puts a point on it with these “three important facts”:

1. The selector list of :is() is forgiving
2. The specificity of :is() is that of its most specific argument
3. :is() does not work with pseudo-element selectors (for now)

Plus, of course, it’s main functionality which is making otherwise rather verbose, complex, and error-prone selectors easier to write. The specificity thing is extra-interesting. Miriam notes some trickery you could do with it, like juicing up specificity without actually selecting anything.

Say you wanted to use the .button class to select, but give it a ton of specificity

:is(.button, #increase#specificity) { /* specificity is now (0, 1, 0, 0) instead of (0, 0, 1, 0) }

I’ve done silly stuff like this in the past:

.button.button.button { /* forcing the selector to be (0, 0, 3, 0) instead of (0, 0, 1, 0) */ /* doesn't actually require element to have three button classes lol */ }

The :is() trick seems a little more understandable to me.

But what if you want to go the other way with specificity and lower it instead? Well, that’s the whole point of the :where() pseudo-selector. Functionally, it’s exactly the same as :is(). You give it a comma-separated list of things to select as part of the selector chain, and it does, with the same forgiving nature. Except, the specificity of the entire :where() part is zero (0).

Kevin showed off an interesting gotcha with :is() in the video:

.card :is(.title, p) { color: red; } .card p { color: yellow; }

You might think yellow will win out here, but the presence of .title in that :is() selector on top makes the specificity of that selector (0, 0, 2, 0) winning out over the (0, 0, 1, 1) below.

This is where we could consider using :where() instead! If we were to use the :where() pseudo-selector instead:

.card :where(.title, p) { color: red; } .card p { color: yellow; }

Then yellow would win, because the top selector lowers to (0, 0, 1, 0) specificity, losing to the bottom selector’s (0, 0, 1, 1).

Which one should you use? Ya know what, I’m not really sure if there is super solid time-tested advice here. At least we have both options available, meaning if you get into a pickle, you’ve got tools to use. Time has taught me that keeping specificity low is generally a healthier place to be. That gives you room to override, where if you’re riding high with specificity you have fewer options. But the zero specificity of :where() is pretty extreme and I could see that leading to confusing moments, too. So my gut is telling me you might wanna start with :is(), unless you notice you need to mix in a higher-specificity selector; if you do, back off to :where().

The post :where() has a cool specificity trick, too. appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Tricking CWV

Css Tricks - Mon, 03/29/2021 - 12:58pm

Google has said that Core Web Vitals (CWV) are going to be an SEO factor, and the date is nigh: May 2021. So, I’m seeing some scrambling to make sure those metrics are good. Ya know, the acronym soup: CLS, LCP, and FID. There is starting to be more and more tooling to measure and diagnose problems. Hopefully, once diagnosed, you have some idea how to fix them. Like if you have crappy CLS, it’s because you load in stuff (probably ads) that shifts layout, and you should either stop doing that or make space for them ahead of time so there is less shifting.

But what about LCP? What if you have this big hero image that is taking a while to paint and it’s giving you a crappy LCP number? Chris Castillo’s trick is to just not load the hero background image at all until a user interacts in some way. Strikes me as weird, but Chris did some light testing and found some users didn’t really notice:

Although this accomplishes the goal, it’s not without a cost. The background image will not load until the user interacts with the screen, so something needs to be used as a fallback until the image can be loaded. I asked a few friends to load the page on their phones and tell me if they found anything strange about the page, and none of them noticed anything “off”. What I observed is that the few friends I asked to test this all had their fingers on the screen or quickly touched the screen when the page was loading, so it happened so quickly they didn’t notice. 

It’s a fine trick that Chris documents, but the point is fooling a machine into giving you better test scores. This feels like the start of a weird new era of web performance where the metrics of web performance have shifted to user-centric measurements, but people are implementing tricky strategies to game those numbers with methods that, if anything, slightly harm user experience.

Direct Link to ArticlePermalink

The post Tricking CWV appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

How to describe element’s natural sizing behavior

Css Tricks - Fri, 03/26/2021 - 11:19am

PPK:

When introducing width and height I explain that by default width takes as much horizontal space as it can, while height takes as little vertical space as possible. This leads to a discussion of these two opposed models that I excerpt below.

My question is: which names do I give to these models?

The three options:

  • inside-out and outside-in
  • context-based and content-based
  • extrinsic and intrinsic size

There is more context in the post.

I definitely don’t like inside-out and outside-in — they make my head spin. I think I’m gonna vote for extrinsic and intrinsic size. I hear those terms thrown around a lot more lately and the fact that they match the specs is something I like. At the same time, I do feel like context-based and content-based are maybe a smidge more clear, but since they are already abstracted and made up, might as well go with the abstracted and made up term that already has legs.

Direct Link to ArticlePermalink

The post How to describe element’s natural sizing behavior appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Want to Write a Hover Effect With Inline CSS? Use CSS Variables.

Css Tricks - Fri, 03/26/2021 - 4:42am

The other day I was working on a blog where each post has a custom color attached to it for a little dose of personality. The author gets to pick that color in the CMS when they’re writing the post. Just a super-light layer of art direction.

To make that color show up on the front end, I wrote the value right into an inline style attribute on the <article> element. My templates happened to be in Liquid, but this would look similar in other templating languages:

{% for post in posts %} <article style="background: {{post.custom_color}}"> <h1>{{post.title}}</h1> {{content}} </article> {% endfor %}

No problem there. But then I thought, “Wouldn’t it be nice if the custom color only showed up when when hovering over the article card?” But you can’t write hover styles in a style attribute, right?

My first idea was to leave the style attribute in place and write CSS like this:

article { background: lightgray !important; } article:hover { /* Doesn't work! */ background: inherit; }

I can override the inline style by using !important, but there’s no way to undo that on hover.

Eventually, I decided I could use a style attribute to get the color value from the CMS, but instead of applying it right away, store it as a CSS variable:

<article style="--custom_color: {{post.custom_color}}"> <h1>{{post.title}}</h1> {{content}} </article>

Then, that variable can be used to define the hover style in regular CSS:

article { background: lightgray; } article:hover { /* Works! */ background: var(--custom_color); }

Now that the color value is saved as a CSS variable, there are all kinds of other things we can do with it. For instance, we could make all links in the post appear in the custom color:

article a { color: var(--custom_color); }

And because the variable is scoped to the <article> element, it won’t affect anything else on the page. We can even display multiple posts on the same page where each one renders in its own custom color.

CodePen Embed Fallback

Browser support for CSS variables is pretty deep, with the exception of Internet Explorer. Anyway, just a neat little trick that might come in handy if you find yourself working with light art direction in a CMS, as well as a reminder of just how awesome CSS variables can be.

The post Want to Write a Hover Effect With Inline CSS? Use CSS Variables. appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Building a Full-Stack Geo-Distributed Serverless App with Macrometa, GatsbyJS, & GitHub Pages

Css Tricks - Thu, 03/25/2021 - 4:15am

In this article, we walk through building out a full-stack real-time and completely serverless application that allows you to create polls! All of the app’s static bits (HTML, CSS, JS, & Media) will be hosted and globally distributed via the GitHub Pages CDN (Content Delivery Network). All of the data and dynamic requests for data (i.e., the back end) will be globally distributed and stateful via the Macrometa GDN (Global Data Network).

Macrometa is a geo-distributed stateful serverless platform designed from the ground up to be lightning-fast no matter where the client is located, optimized for both reads and writes, and elastically scalable. We will use it as a database for data collection and maintaining state and stream to subscribe to database updates for real-time action.

We will be using Gatsby to manage our app and deploy it to Github Pages. Let’s do this!

Intro

This demo uses the Macrometa c8db-source-plugin to get some of the data as markdown and then transform it to HTML to display directly in the browser and the Macrometa JSC8 SKD to keep an open socket for real-time fun and manage working with Macrometa’s API.

Getting started
  1. Node.js and npm must be installed on your machine.
  2. After you have that done, install the Gatsby-CLI >> npm install -g gatsby-cli
  3. If you don’t have one already go ahead and signup for a free Macrometa developer account.
  4. Once you’re logged in to Macrometa create a document collection called markdownContent. Then create a single document with title and content fields in markdown format. This creates your data model the app will be using for its static content.

Here’s an example of what the markdownContent collection should look like:

{ "title": "## Real-Time Polling Application", "content": "Full-Stack Geo-Distributed Serverless App Built with GatsbyJS and Macrometa!" }

content and title keys in the document are in the markdown format. Once they go through gatsby-source-c8db, data in title is converted to <h2></h2>, and content to <p></p>.

  1. Now create a second document collection called polls. This is where the poll data will be stored.

In the polls collection each poll will be stored as a separate document. A sample document is mentioned below:

{ "pollName": "What is your spirit animal?", "polls": [ { "editing": false, "id": "975e41", "text": "dog", "votes": 2 }, { "editing": false, "id": "b8aa60", "text": "cat", "votes": 1 }, { "editing": false, "id": "b8aa42", "text": "unicorn", "votes": 10 } ] } Setting up auth

Your Macrometa login details, along with the collection to be used and markdown transformations, has to be provided in the application’s gatsby-config.js like below:

{ resolve: "gatsby-source-c8db", options: { config: "https://gdn.paas.macrometa.io", auth: { email: "<my-email>", password: "process.env.MM_PW" }, geoFabric: "_system", collection: 'markdownContent', map: { markdownContent: { title: "text/markdown", content: "text/markdown" } } } }

Under password you will notice that it says process.env.MM_PW, instead of putting your password there, we are going to create some .env files and make sure that those files are listed in our .gitignore file, so we don’t accidentally push our Macrometa password back to Github. In your root directory create .env.development and .env.production files.

You will only have one thing in each of those files: MM_PW='<your-password-here>'

Running the app locally

We have the frontend code already done, so you can fork the repo, set up your Macrometa account as described above, add your password as described above, and then deploy. Go ahead and do that and then I’ll walk you through how the app is set up so you can check out the code.

In the terminal of your choice:

  1. Fork this repo and clone your fork onto your local machine
  2. Run npm install
  3. Once that’s done run npm run develop to start the local server. This will start local development server on http://localhost:<some_port> and the GraphQL server at http://localhost:<some_port>/___graphql
How to deploy app (UI) on GitHub Pages

Once you have the app running as expected in your local environment simply run npm run deploy!

Gatsby will automatically generate the static code for the site, create a branch called gh-pages, and deploy it to Github.

Now you can access your site at <your-github-username>.github.io/tutorial-jamstack-pollingapp

If your app isn‘t showing up there for some reason go check out your repo’s settings and make sure Github Pages is enabled and configured to run on your gh-pages branch.

Walking through the code

First, we made a file that loaded the Macrometa JSC8 Driver, made sure we opened up a socket to Macrometa, and then defined the various API calls we will be using in the app. Next, we made the config available to the whole app.

After that we wrote the functions that handle various front-end events. Here’s the code for handling a vote submission:

onVote = async (onSubmitVote, getPollData, establishLiveConnection) => { const { title } = this.state; const { selection } = this.state; this.setState({ loading: true }, () => { onSubmitVote(selection) .then(async () => { const pollData = await getPollData(); this.setState({ loading: false, hasVoted: true, options: Object.values(pollData) }, () => { // open socket connections for live updates const onmessage = msg => { const { payload } = JSON.parse(msg); const decoded = JSON.parse(atob(payload)); this.setState({ options: decoded[title] }); } establishLiveConnection(onmessage); }); }) .catch(err => console.log(err)) }); } You can check out a live example of the app here

You can create your own poll. To allow multiple people to vote on the same topic just share the vote URL with them.

Try Macrometa

The post Building a Full-Stack Geo-Distributed Serverless App with Macrometa, GatsbyJS, & GitHub Pages appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS terminology question

QuirksBlog - Thu, 03/25/2021 - 1:32am

I need to figure out how to call a pair of fundamental CSS concepts in my upcoming book and would like to hear which option you think is best — or least confusing.

When introducing width and height I explain that by default width takes as much horizontal space as it can, while height takes as little vertical space as possible. This leads to a discussion of these two opposed models that I excerpt below.

My question is: which names do I give to these models?

  1. The excerpt uses the names inside-out and outside-in — the ones I decided to use initially. However, a Twitter thread revealed two other good options:
  2. context-based and content-based
  3. extrinsic and intrinsic size, which I think are the terms used in the specifications. I'm afraid they may seem daunting to novices — you need to know what extrinsic and intrinsic mean.

Results: Content- and context-base 130 votes (62%); intrinsic and extrinsic 53 votes (25%); inside-out and outside-in 26 votes (12%).

So the terms will be content-based and context-based. Duly noted.

Excerpt from the Flow chapter

Here follows an excerpt from my current draft.

[end of previous section]

We’ll get back to setting widths and heights later [in the book]. For now it’s enough to note that their default behavior is quite different: width takes as much horizontal space as it can, while height takes as little vertical space as it can. Understanding this difference is key to understanding the browsers’ default layout.

Inside-out and outside-in

An important purpose of any layout module is figuring out what dimensions blocks should take. Why does a block have the width and height that it has? How do browsers determine that?

There are two models for determining block sizes: the outside-in model and the inside-out model. Width works outside-in, while height works inside-out. What does that mean?

In the outside-in model the block looks at the context outside itself to figure out which dimensions it should have. For instance, the default width of a paragraph is determined by the width of its container. If that container width changes, for instance because the user resizes the browser window, the width of the paragraphs changes in reaction to these factors outside themselves. Grid layout is another example of outside-in: it gives a central definition of the columns and rows that all blocks obey. If that definition changes, the blocks’ dimensions also change, regardless of their content.

In the inside-out model the block looks at its content to figure out which dimensions it should take. For instance, the default height of a paragraph is determined by the amount of text it contains. If a script adds more text, the paragraph’s height grows in reaction to these factors inside it. Flexbox layout is another example of inside-out: all individual items in a flexbox look at their contents first to determine their width. If the content of an item changes, the item’s width also changes, regardless of its neighbors.

If a script adds text to a paragraph its width stays the same. Width does not concern itself with what happens inside the paragraph; only with external factors.

Inside-out is more complex than outside-in because indirectly it may also react to outside factors. When the user makes the browser window narrower the width of the paragraphs decreases, which means less text fits on one line, which means the text needs more lines, which means the height of the paragraphs increases. Height is still inside-out, though: the reason the height changes is that the contents of the paragraphs need more space.The fact that they need more space because of the outside-in changes to the width is irrelevant to the height calculation.

As a general rule, when you create a layout you set outside-in dimensions such as width and grid fairly strictly, while you generally leave inside-out dimensions such as height and the width of flex items alone. Like any general rule this one has plenty of exceptions, and we'll encounter some of them in later chapters. Still, if you're unfamilier with CSS layout I advise you to stick to setting outside-in sizes, and allow the browser to set inside-out sizes for itself. That will make your life a lot easier.

Excerpt ends.

Maps Scroll Wheel Fix

Css Tricks - Tue, 03/23/2021 - 1:51pm

This blog post by Steve Fenton came across my feeds the other day. I’d never heard of HERE maps before, but apparently they are embeddable somehow, like Google Maps. The problem is that you zoom and and out of HERE maps with the scrollwheel. So imagine you’re scrolling down a page, your cursor (or finger) ends up on the HERE map, and now you can’t continue scrolling down the page because that scrolling event is captured by the map and turns into map zooming.

Steve’s solution: put a “coverer” <div> over the map when a scroll event starts on the window, and remove it after a short delay (when scrolling “stops”). That solution resonates with me, as not only have I coded solutions like that in the past for embedded maps, we have a solution like that in place on CodePen today. On CodePen, you can resize the “preview” window, which is an <iframe> of the code you write. If you drag too swiftly, your mouse cursor (or touch event) might trigger movement off of the draggable element, possible onto the <iframe> itself. If that happens, the <iframe> will swallow the event, and the resizing you are trying to do stops working correctly. To prevent this, we put a “covered” <div> over top the <iframe> while you are dragging, and remove it when you stop dragging.

Thinking of maps though, it reminds me Brad Frost’s Adaptive Maps idea documented back in 2012. The idea is that embedding a map on a small screen mobile device just isn’t a good idea. Space is cramped, they can slow down page load, and, like Steve experienced nearly a decade later, they can mess with users scrolling through the page. Brads solution is to serve an image of a map (which can still be API-driven) conditionally for small screens with a “View Map” link that takes them to a full-screen map experience, probably within the map native app itself. Large screens can still have the interactive map, although, I might argue that having the image-that-links-to-map-service might be a smart pattern for any browser with less technical debt.

The post Maps Scroll Wheel Fix appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Imagining native skip links

Css Tricks - Tue, 03/23/2021 - 11:32am

I love it when standards evolve from something that a bunch of developers are already doing, and making it easier and foolproof. Kitty Giraudel is onto that here with skip links, something that every website should probably have, and that has a whole checklist of things that we can and do screw up:

  • It should be the first thing to tab into.
  • It should be hidden carefully so it remains focusable.
  • When focused, it should become visible.
  • Its content should start with “Skip” to be easily recognisable.
  • It should lead to the main content of the page.

Doing this natively could solve all those problems and more (like displaying in the correct language for that user). Nice little project for someone to mock up as a browser extension, I’d say.

Reminds me of the idea of extending the Web Share API into native HTML. It’s just a good idea.

Direct Link to ArticlePermalink

The post Imagining native skip links appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

An Event Apart Spring Summit 2021

Css Tricks - Mon, 03/22/2021 - 9:26am

Hey, look at that, An Event Apart is back with a new event taking place online from April 19-21. That’s three jam-packed days of absolute gems from a stellar lineup of speakers!

Guess what? I’m going to be there, along with my ShopTalk Show co-host Dave Rupert doing a live show which could include questions and comments from you. Dave will be doing a talk as well, on Web Components, which I’ll be in the virtual front row for.

What else? You’ll learn about advanced CSS from Rachel Andrew and Miriam Suzanne (believe me, there is a lot going on in CSS land to know about), inclusive and cross-cultural design from Derek Featherstone and Senongo Akpem, PWAs from Ire Aderinokun, user research from Cyd Harrell, and much, much more. Huge. Check out the detailed Spring Summit three-day schedule and prepare to be wowed by all the names on that list.

You can join the fun by registering today. An Event Apart actually gave us a discount code just for CSS-Tricks readers like yourself. Use AEACSST21 at checkout and that’ll knock $100 of the price of a multi-day pass.

Register Today

The post An Event Apart Spring Summit 2021 appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Platform News: Prefers Contrast, MathML, :is(), and CSS Background Initial Values

Css Tricks - Fri, 03/19/2021 - 5:05am

In this week’s round-up, prefers-contrast lands in Safari, MathML gets some attention, :is() is actually quite forgiving, more ADA-related lawsuits, inconsistent initial values for CSS Backgrounds properties can lead to unwanted — but sorta neat — patterns.

The prefers-contrast: more media query is supported in Safari Preview

After prefers-reduced-motion in 2017, prefers-color-scheme in 2019, and forced-colors in 2020, a fourth user preference media feature is making its way to browsers. The CSS prefers-contrast: more media query is now supported in the preview version of Safari. This feature will allow websites to honor a user’s preference for increased contrast.

Apple could use this new media query to increase the contrast of gray text on its website .pricing-info { color: #86868b; /* contrast ratio 3.5:1 */ } @media (prefers-contrast: more) { .pricing-info { color: #535283; /* contrast ratio 7:1 */ } } Making math a first-class citizen on the web

One of the earliest specifications developed by the W3C in the mid-to-late ’90s was a markup language for displaying mathematical notations on the web called MathML. This language is currently supported in Firefox and Safari. Chrome’s implementation was removed in 2013 because of “concerns involving security, performance, and low usage on the Internet.”

CodePen Embed Fallback

If you’re using Chrome or Edge, enable “Experimental Web Platform features” on the about:flags page to view the demo.

There is a renewed effort to properly integrate MathML into the web platform and bring it to all browsers in an interoperable way. Igalia has been developing a MathML implementation for Chromium since 2019. The new MathML Core Level 1 specification is a fundamental subset of MathML 3 (2014) that is “most suited for browser implementation.” If approved by the W3C, a new Math Working Group will work on improving the accessibility and searchability of MathML.

The mission of the Math Working Group is to promote the inclusion of mathematics on the Web so that it is a first-class citizen of the web that displays well, is accessible, and is searchable.

CSS :is() upgrades selector lists to become forgiving

The new CSS :is() and :where() pseudo-classes are now supported in Chrome, Safari, and Firefox. In addition to their standard use cases (reducing repetition and keeping specificity low), these pseudo-classes can also be used to make selector lists “forgiving.”

For legacy reasons, the general behavior of a selector list is that if any selector in the list fails to parse […] the entire selector list becomes invalid. This can make it hard to write CSS that uses new selectors and still works correctly in older user agents.

In other words, “if any part of a selector is invalid, it invalidates the whole selector.” However, wrapping the selector list in :is() makes it forgiving: Unsupported selectors are simply ignored, but the remaining selectors will still match.

Unfortunately, pseudo-elements do not work inside :is() (although that may change in the future), so it is currently not possible to turn two vendor-prefixed pseudo-elements into a forgiving selector list to avoid repeating styles.

/* One unsupported selector invalidates the entire list */ ::-webkit-slider-runnable-track, ::-moz-range-track { background: red; } /* Pseudo-elements do not work inside :is() */ :is(::-webkit-slider-runnable-track, ::-moz-range-track) { background: red; } /* Thus, the styles must unfortunately be repeated */ ::-webkit-slider-runnable-track { background: red; } ::-moz-range-track { background: red; } Dell and Kraft Heinz sued over inaccessible websites

More and more American businesses are facing lawsuits over accessibility issues on their websites. Most recently, the tech corporation Dell was sued by a visually impaired person who was unable to navigate Dell’s website and online store using the JAWS and VoiceOver screen readers.

The Defendant fails to communicate information about its products and services effectively because screen reader auxiliary aids cannot access important content on the Digital Platform. […] The Digital Platform uses visual cues to convey content and other information. Unfortunately, screen readers cannot interpret these cues and communicate the information they represent to individuals with visual disabilities.

Earlier this year, Kraft Heinz Foods Company was sued for failing to comply with the Web Content Accessibility Guidelines on one of the company’s websites. The complaint alleges that the website did not declare a language (lang attribute) and provide accessible labels for its image links, among other things.

In the United States, the Americans with Disabilities Act (ADA) applies to websites, which means that people can sue retailers if their websites are not accessible. According to the CEO of Deque Systems (the makers of axe), the recent increasing trend of web-based ADA lawsuits can be attributed to a lack of a single overarching regulation that would provide specific compliance requirements.

background-clip and background-origin have different initial values

By default, a CSS background is painted within the element’s border box (background-clip: border-box) but positioned relative to the element’s padding box (background-origin: padding-box). This inconsistency can result in unexpected patterns if the element’s border is semi-transparent or dotted/dashed.

.box { /* semi-transparent border */ border: 20px solid rgba(255, 255, 255, 0.25); /* background gradient */ background: conic-gradient( from 45deg at bottom left, deeppink, rebeccapurple ); }

Because of the different initial values, the background gradient in the above image is repeated as a tiled image on all sides under the semi-transparent border. In this case, positioning the background relative to the border box (background-origin: border-box) makes more sense.

CodePen Embed Fallback

The post Platform News: Prefers Contrast, MathML, :is(), and CSS Background Initial Values appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

The Mobile Performance Inequality Gap

Css Tricks - Thu, 03/18/2021 - 9:26am

Alex Russell made some interesting notes about performance and how it impacts folks on mobile:

[…] CPUs are not improving fast enough to cope with frontend engineers’ rosy resource assumptions. If there is unambiguously good news on the tooling front, multiple popular tools now include options to prevent sending first-party JS in the first place (Next.jsGatsby), though the JS community remains in stubborn denial about the costs of client-side script. Hopefully, toolchain progress of this sort can provide a more accessible bridge as we transition costs to a reduced-script-emissions world.

A lot of the stuff I read when it comes to performance is focused on America, but what I like about Russell’s take here is that he looks at a host of other countries such as India, too. But how does the rollout of 5G networks impact performance around the world? Well, we should be skeptical of how improved networks impact our work. Alex argues:

5G looks set to continue a bumpy rollout for the next half-decade. Carriers make different frequency band choices in different geographies, and 5G performance is heavily sensitive to mast density, which will add confusion for years to come. Suffice to say, 5G isn’t here yet, even if wealthy users in a few geographies come to think of it as “normal” far ahead of worldwide deployment

This is something I try to keep in mind whenever I’m thinking about performance: how I’m viewing my website is most likely not how other folks are viewing it.

Direct Link to ArticlePermalink

The post The Mobile Performance Inequality Gap appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Handling User Permissions in JavaScript

Css Tricks - Wed, 03/17/2021 - 4:53am

So, you have been working on this new and fancy web application. Be it a recipe app, a document manager, or even your private cloud, you‘ve now reached the point of working with users and permissions. Take the document manager as an example: you don’t just want admins; maybe you want to invite guests with read-only access or people who can edit but not delete your files. How do you handle that logic in the front end without cluttering your code with too many complicated conditions and checks?

In this article, we will go over an example implementation on how you could handle these kinds of situation in an elegant and clean manner. Take it with a grain of salt – your needs might differ, but I hope that you can gain some ideas from it.

Let‘s assume that you already built the back end, added a table for all users in your database, and maybe provided a dedicated column or property for roles. The implementation details are totally up to you (depending on your stack and preference). For the sake of this demo, let’s use the following roles:

  • Admin: can do anything, like creating, deleting, and editing own or foreign documents.
  • Editor: can create, view, and edit files but not delete them.
  • Guest: can view files, simple as that.

Like most modern web applications out there, your app might use a RESTful API to communicate with the back end, so let’s use this scenario for the demo. Even if you go with something different like GraphQL or server-side rendering, you can still apply the same pattern we are going to look at.

The key is to return the role (or permission, if you prefer that name) of the currently logged-in user when fetching some data.

{ id: 1, title: "My First Document", authorId: 742, accessLevel: "ADMIN", content: {...} }

Here, we fetch a document with some properties, including a property called accessLevel for the user’s role. That’s how we know what the logged-in user is allowed or not allowed to do. Our next job is to add some logic in the front end to ensure that guests don‘t see things they’re not supposed to, and vice-versa.

Ideally, you don’t only rely on the frontend to check permissions. Someone experienced with web technologies could still send a request without UI to the server with the intent to manipulate data, hence your backend should be checking things as well.

By the way, this pattern is framework agnostic; it doesn’t matter if you work with React, Vue, or even some wild Vanilla JavaScript.

Defining constants

The very first (optional, but highly recommended) step is to create some constants. These will be simple objects that contain all actions, roles, and other important parts that the app might consist of. I like to put them into a dedicated file, maybe call it constants.js:

const actions = { MODIFY_FILE: "MODIFY_FILE", VIEW_FILE: "VIEW_FILE", DELETE_FILE: "DELETE_FILE", CREATE_FILE: "CREATE_FILE" }; const roles = { ADMIN: "ADMIN", EDITOR: "EDITOR", GUEST: "GUEST" }; export { actions, roles };

If you have the advantage of using TypeScript, you can use enums to get a slightly cleaner syntax.

Creating a collection of constants for your actions and roles has some advantages:

  • One single source of truth. Instead of looking through your entire codebase, you simply open constants.js to see what’s possible inside your app. This approach is also very extensible, say when you add or remove actions.
  • No typing errors. Instead of hand-typing a role or action each time, making it prone to typos and unpleasant debugging sessions, you import the object and, thanks to your favorite editor’s magic, get suggestions and auto-completion for free. If you still mistype a name, ESLint or some other tool will most likely yell at you until you fix it.
  • Documentation. Are you working in a team? New team members will appreciate the simplicity of not needing to go through tons of files to understand what permissions or actions exist. It can also be easily documented with JSDoc.

Using these constants is pretty straight-forward; import and use them like so:

import { actions } from "./constants.js"; console.log(actions.CREATE_FILE); Defining permissions

Off to the exciting part: modeling a data structure to map our actions to roles. There are many ways to solve this problem, but I like the following one the most. Let’s create a new file, call it permissions.js, and put some code inside:

import { actions, roles } from "./constants.js"; const mappings = new Map(); mappings.set(actions.MODIFY_FILE, [roles.ADMIN, roles.EDITOR]); mappings.set(actions.VIEW_FILE, [roles.ADMIN, roles.EDITOR, roles.GUEST]); mappings.set(actions.DELETE_FILE, [roles.ADMIN]); mappings.set(actions.CREATE_FILE, [roles.ADMIN, roles.EDITOR]);

Let’s go through this, step-by-step:

  • First, we need to import our constants.
  • We then create a new JavaScript Map, called mappings. We could’ve gone with any other data structure, like objects, arrays, you name it. I like to use Maps, since they offer some handy methods, like .has(), .get(), etc.
  • Next, we add (or rather set) a new entry for each action our app has. The action serves as the key, by which we then get the roles required to execute said action. As for the value, we define an array of necessary roles.

This approach might seem strange at first (it did to me), but I learned to appreciate it over time. The benefits are evident, especially in larger applications with tons of actions and roles:

  • Again, only one source of truth. Do you need to know what roles are required to edit a file? No problem, head over to permissions.js and look for the entry.
  • Modifying business logic is surprisingly simple. Say your product manager decides that, from tomorrow on, editors are allowed to delete files; simply add their role to the DELETE_FILE entry and call it a day. The same goes for adding new roles: add more entries to the mappings variable, and you’re good to go.
  • Testable. You can use snapshot tests to make sure that nothing changes unexpectedly inside these mappings. It’s also clearer during code reviews.

The above example is rather simple and could be extended to cover more complicated cases. If you have different file types with different role access, for example. More on that at the end of this article.

Checking permissions in the UI

We defined all of our actions and roles and we created a map that explains who is allowed to do what. It’s time to implement a function for us to use in our UI to check for those roles.

When creating such new behavior, I always like to start with how the API should look. Afterwards, I implement the actual logic behind that API.

Say we have a React Component that renders a dropdown menu:

function Dropdown() { return ( <ul> <li><button type="button">Refresh</button><li> <li><button type="button">Rename</button><li> <li><button type="button">Duplicate</button><li> <li><button type="button">Delete</button><li> </ul> ); }

Obviously, we don’t want guests to see nor click the option “Delete” or “Rename,” but we want them to see “Refresh.” On the other hand, editors should see all but “Delete.” I imagine some API like this:

hasPermission(file, actions.DELETE_FILE);

The first argument is the file itself, as fetched by our REST API. It should contain the accessLevel property from earlier, which can either be ADMIN, EDITOR, or GUEST. Since the same user might have different permissions in different files, we always need to provide that argument.

As for the second argument, we pass an action, like deleting the file. The function should then return a boolean true if the currently logged-in user has permissions for that action, or false if not.

import hasPermission from "./permissions.js"; import { actions } from "./constants.js"; function Dropdown() { return ( <ul> {hasPermission(file, actions.VIEW_FILE) && ( <li><button type="button">Refresh</button></li> )} {hasPermission(file, actions.MODIFY_FILE) && ( <li><button type="button">Rename</button></li> )} {hasPermission(file, actions.CREATE_FILE) && ( <li><button type="button">Duplicate</button></li> )} {hasPermission(file, actions.DELETE_FILE) && ( <li><button type="button">Delete</button></li> )} </ul> ); }

You might want to find a less verbose function name or maybe even a different way to implement the entire logic (currying comes to mind), but for me, this has done a pretty good job, even in applications with super complex permissions. Sure, the JSX looks more cluttered, but that’s a small price to pay. Having this pattern consistently used across the entire app makes permissions a lot cleaner and more intuitive to understand.

In case you are still not convinced, let’s see how it would look without the hasPermission helper:

return ( <ul> {['ADMIN', 'EDITOR', 'GUEST'].includes(file.accessLevel) && ( <li><button type="button">Refresh</button></li> )} {['ADMIN', 'EDITOR'].includes(file.accessLevel) && ( <li><button type="button">Rename</button></li> )} {['ADMIN', 'EDITOR'].includes(file.accessLevel) && ( <li><button type="button">Duplicate</button></li> )} {file.accessLevel == "ADMIN" && ( <li><button type="button">Delete</button></li> )} </ul> );

You might say that this doesn’t look too bad, but think about what happens if more logic is added, like license checks or more granular permissions. Things tend to get out of hand quickly in our profession.

Are you wondering why we need the first permission check when everybody may see the “Refresh” button anyways? I like to have it there because you never know what might change in the future. A new role might get introduced that may not even see the button. In that case, you only have to update your permissions.js and get to leave the component alone, resulting in a cleaner Git commit and fewer chances to mess up.

Implementing the permission checker

Finally, it’s time to implement the function that glues it all together: actions, roles, and the UI. The implementation is pretty straightforward:

import mappings from "./permissions.js"; function hasPermission(file, action) { if (!file?.accessLevel) { return false; } if (mappings.has(action)) { return mappings.get(action).includes(file.accessLevel); } return false; } export default hasPermission; export { actions, roles };

You can put the above code into a separate file or even within permissions.js. I personally keep them together in one file but, hey, I am not telling you how to live your life. :-)

Let’s digest what’s happening here:

  1. We define a new function, hasPermission, using the same API signature that we decided on earlier. It takes the file (which comes from the back end) and the action we want to perform.
  2. As a fail-safe, if, for some reason, the file is null or doesn’t contain an accessLevel property, we return false. Better be extra careful not to expose “secret” information to the user caused by a glitch or some error in the code.
  3. Coming to the core, we check if mappings contains the action that we are looking for. If so, we can safely get its value (remember, it’s an array of roles) and check if our currently logged-in user has the role required for that action. This either returns true or false.
  4. Finally, if mappings didn’t contain the action we are looking for (could be a mistake in the code or a glitch again), we return false to be extra safe.
  5. On the last two lines, we don’t only export the hasPermission function but also re-export our constants for developer convenience. That way, we can import all utilities in one line.
import hasPermission, { actions } from "./permissions.js"; More use cases

The shown code is quite simple for demonstration purposes. Still, you can take it as a base for your app and shape it accordingly. I think it’s a good starting point for any JavaScript-driven application to implement user roles and permissions.

With a bit of refactoring, you can even reuse this pattern to check for something different, like licenses:

import { actions, licenses } from "./constants.js"; const mappings = new Map(); mappings.set(actions.MODIFY_FILE, [licenses.PAID]); mappings.set(actions.VIEW_FILE, [licenses.FREE, licenses.PAID]); mappings.set(actions.DELETE_FILE, [licenses.FREE, licenses.PAID]); mappings.set(actions.CREATE_FILE, [licenses.PAID]); function hasLicense(user, action) { if (mappings.has(action)) { return mappings.get(action).includes(user.license); } return false; }

Instead of a user’s role, we assert their license property: same input, same output, completely different context.

In my team, we needed to check for both user roles and licenses, either together or separately. When we chose this pattern, we created different functions for different checks and combined them in a wrapper. What we ended up using was a hasAccess util:

function hasAccess(file, user, action) { return hasPermission(file, action) && hasLicense(user, action); }

It’s not ideal to pass three arguments each time you call hasAccess, and you might find a way around that in your app (like currying or global state). In our app, we use global stores that contain the user’s information, so we can simply remove the second argument and get that from a store instead.

You can also go deeper in terms of permission structure. Do you have different types of files (or entities, to be more general)? Do you want to enable certain file types based on the user‘s license? Let’s take the above example and make it slightly more powerful:

const mappings = new Map(); mappings.set( actions.EXPORT_FILE, new Map([ [types.PDF, [licenses.FREE, licenses.PAID]], [types.DOCX, [licenses.PAID]], [types.XLSX, [licenses.PAID]], [types.PPTX, [licenses.PAID]] ]) );

This adds a whole new level to our permission checker. Now, we can have different types of entities for one single action. Let‘s assume that you want to provide an exporter for your files, but you want your users to pay for that super-fancy Microsoft Office converter that you’ve built (and who could blame you?). Instead of directly providing an array, we nest a second Map inside the action and pass along all file types that we want to cover. Why using a Map, you ask? For the same reason I mentioned earlier: it provides some friendly methods like .has(). Feel free to use something different, though.

With the recent change, our hasLicense function doesn’t cut it any longer, so it’s time to update it slightly:

function hasLicense(user, file, action) { if (!user || !file) { return false; } if (mappings.has(action)) { const mapping = mappings.get(action); if (mapping.has(file.type)) { return mapping.get(file.type).includes(user.license); } } return false; }

I don’t know if it’s just me, but doesn’t that still look super readable, even though the complexity has increased?

Testing

If you want to ensure that your app works as expected, even after code refactorings or the introduction of new features, you better have some test coverage ready. In regards to testing user permissions, you can use different approaches:

  • Create snapshot tests for mappings, actions, types, etc. This can be achieved easily in Jest or other test runners and ensures that nothing slips unexpectedly through the code review. It might get tedious to update these snapshots if permissions change all the time, though.
  • Add unit tests for hasLicense or hasPermission and assert that the function is working as expected by hard-coding some real-world test cases. Unit-testing functions is mostly, if not always, a good idea as you want to ensure that the correct value is returned.
  • Besides ensuring that the internal logic works, you can use additional snapshot tests in combination with your constants to cover every single scenario. My team uses something similar to this:
Object.values(actions).forEach((action) => { describe(action.toLowerCase(), function() { Object.values(licenses).forEach((license) => { it(license.toLowerCase(), function() { expect(hasLicense({ type: 'PDF' }, { license }, action)).toMatchSnapshot(); expect(hasLicense({ type: 'DOCX' }, { license }, action)).toMatchSnapshot(); expect(hasLicense({ type: 'XLSX' }, { license }, action)).toMatchSnapshot(); expect(hasLicense({ type: 'PPTX' }, { license }, action)).toMatchSnapshot(); }); }); }); });

But again, there’re many different personal preferences and ways to test it.

Conclusion

And that’s it! I hope you were able to gain some ideas or inspiration for your next project and that this pattern might be something you want to reach for. To recap some of its advantages:

  • No more need for complicated conditions or logic in your UI (components). You can rely on the hasPermission function’s return value and comfortably show and hide elements based on that. Being able to separate business logic from your UI helps with a cleaner and more maintainable codebase.
  • One single source of truth for your permissions. Instead of going through many files to figure out what a user can or cannot see, head into the permissions mappings and look there. This makes extending and changing user permissions a breeze since you might not even need to touch any markup.
  • Very testable. Whether you decide on snapshot tests, integration tests with other components, or something else, the centralized permissions are painless to write tests for.
  • Documentation. You don’t need to write your app in TypeScript to benefit from auto-completion or code validation; using predefined constants for actions, roles, licenses, and such can simplify your life and reduce annoying typos. Also, other team members can easily spot what actions, roles, or whatever are available and where they are being used.

Suppose you want to see a complete demonstration of this pattern, head over to this CodeSandbox that plays around with the idea using React. It includes different permission checks and even some test coverage.

What do you think? Do you have a similar approach to such things and do you think it’s worth the effort? I am always interested in what other people came up with, feel free to post any feedback in the comment section. Take care!

The post Handling User Permissions in JavaScript appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Long Hover

Css Tricks - Tue, 03/16/2021 - 12:59pm

I had a very embarrassing CSS moment the other day.

I was working on the front-end code of a design that had a narrow sidebar of icons. There isn’t enough room there to show text of what the icons are, so the idea is that we’ll use accessible (but visually hidden, by default) text that is in there already as a tooltip on a “long hover.” That is, a device with a cursor, and the cursor hovering over the element for a while, like three seconds.

So, my mind went like this…

  1. I can use state: the tooltip is either visible or not visible. I’ll manage the state, which will manifest in the DOM as a class name on an HTML element.
  2. Then I’ll deal with the logic for changing that state.
  3. The default state will be not visible, but if the mouse is inside the element for over three seconds, I’ll switch the state to visible.
  4. If the mouse ever leaves the element, the state will remain (or become) not visible.

This was a React project, so state was just on the mind. That ended up like this:

CodePen Embed Fallback

Not that bad, right? Eh. Having state managed in JavaScript does potentially open some doors, but in this case, it was total overkill. Aside from the fact that I find mouseenter and mouseleave a little finicky, CSS could have done the entire thing, and with less code.

That’s the embarrassing part… why would I reach up the chain to a JavaScript library to do this when the CSS that I’m already writing can handle it?

I’ll leave the UI in React, but rip out all the state management stuff. All I’ll do is add a transition-delay: 3s when the .icon is :hover so that it’s zero seconds when not hovered, then goes away immediately when the mouse cursor leaves).

CodePen Embed Fallback

A long hover is basically a one-liner in CSS:

.thing { transition: 0.2s; } .thing:hover { transition-delay: 3s; /* delay hover animation only ON, not OFF */ }

Works great.

One problem that isn’t addressed here is the touch screen problem. You could argue screen readers are OK with the accessible text and desktop browsers are OK because of the custom tooltips, but users with touch-only screens might be unable to discover the icon labels. In my case, I was building for a large screen scenario that assumes cursors, but I don’t think all-is-lost for touch screens. If the element is a link, the :hover might fire on first-tap anyway. If the link takes you somewhere with a clear title, that might be enough context. And you can always go back to more JavaScript and handle touch events.

The post Long Hover appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Syndicate content
©2003 - Present Akamai Design & Development.