The Autofill Dark Pattern

A newspaper sign-up form had fields for name, email, and password. So, I started typing on the name field, and the autofill suggested my profile. But there was something funky. The autocomplete suggestion included my mailing address. Needless to say, it was puzzling: the address was not a field in the form. Why was it even suggested?

By the time this question started forming in my mind, my brain had already signaled my finger to click on the suggestion, and it was done. Next, I was taken to a second form page which requested additional information like address, phone, date of birth, and so on. And all those fields had been pre-populated by the autofill functionality, too.

I sighed in relief. It was a “multi-stepped” form and not a trick by the website. It was a reputable newspaper, after all. I deleted all the optional information from the second page, completed the sign-up, and moved on.

That (troubled) interaction highlighted one of the risks of using autocompletion features.

Autocomplete And Autofill

They may sound similar, but autocomplete and autofill are not the same thing. Although they are closely related:

  • Autofill is a browser feature that allows people to save information (on the browser or the OS) and use it on web forms.
  • autocomplete is an HTML attribute that provides guidelines to the browser on how to (or not to) autofill in fields in a web form.

We could say that autofill is the “what,” while autocomplete the “how”, i.e. autofill stores data and tries to match it in a web form (based on the fields’ name, type, or id), and autocomplete guides the browser on how to do it (what information is expected in each field).

Autocomplete is a powerful feature with many options that allows specifying many different types of values:

  • Personal: Name, address, phone, date of birth;
  • Financial: credit card number, name, expiration date;
  • Demographics: location, age, sex, language;
  • Professional: company and job title.

Autofill is a widespread feature either by choice or by accident: who hasn’t accepted to let the browser save/use web form information, either on purpose or by mistake? And that could be a problem — especially combined with bad use of autocomplete (and the added outrageous number of phishing emails and SMS messages nowadays.)

Privacy Risks

Both of these features present (at least) two main risks for the user, both related to their personal data and its privacy:

  1. Non-visible fields are populated (this is not the same as fields with a hidden type);
  2. Autocompleted information can be read via JavaScript even before the user submits the form.

This means that once a user selects to autofill the information, all the fields will be available for the developer to read. Again, independently of the user submitting the form or not, without the user knowing what fields were actually populated.

This last part is relative: knowing what fields are populated will depend on the browser. Safari and Firefox do a good job at this (as we’ll soon see below). On the other hand, Chrome, the most popular browser at the moment, offers a bad experience that may trick even the most knowledgeable users into sharing their personal information.

If we also consider the times in which the user accidentally chooses to populate the fields, this issue becomes more relevant. Let’s check it in more detail with an example.

A Little Experiment

I ran a little experiment creating a form with many fields and attaching the autocomplete attribute with different values. Then, I played a little with the form’s structure:

  • I hid most of the fields by putting them in a container offscreen (instead of using hidden or type="hidden");
  • I removed the visually hidden fields from the tab order (so keyboard users would overlook the hidden fields);
  • I tried sorting the fields in a different order (and to my surprise, this impacted the autofill!).

In the end, the code for the form looked like this:

<form method="post" action="javascript:alertData()">
  <label for="name">Full name</label><input id="name" name="name" autocomplete="name" /><br/>
  <label for="email">Email</label><input id="email" name="email"/><br/>
  <label for="postal-code">ZIP</label><input id="postal-code" name="postal-code" autocomplete="postal-code"/>
  <div style="position:absolute;top:-10000in" class="hide-this">
    <!-- Hidden -->
    <label for="firstname">First name</label><input tabindex="-1" type="hidden" id="firstname" name="firstname" autocomplete="given-name" /><br/>
    <label for="lastname">Last name</label><input tabindex="-1"  id="lastname" name="lastname" autocomplete="family-name" /><br/>

    <label for="honorific-prefix">honorific-prefix</label><input tabindex="-1" id="honorific-prefix" name="honorific-prefix" autocomplete="honorific-prefix"/><br/>
    <label for="organization">Organization</label><input tabindex="-1" id="organization" name="organization" /><br/>
    <label for="phone">Phone</label><input tabindex="-1" id="phone" name="phone" autocomplete="tel" /><br/>

    <label for="address">address</label><input tabindex="-1" id="address" name="address" autocomplete="street-address" /><br/>
    <label for="city">City</label><input tabindex="-1" id="city" name="city" autocomplete="address-level2" /><br/>
    <label for="state">State</label><input tabindex="-1" id="state" name="state" autocomplete="address-level1" /><br/>
    <label for="level3">Level3</label><input tabindex="-1" id="level3" name="state" autocomplete="address-level3" /><br/>
    <label for="level4">Level4</label><input tabindex="-1" id="level4" name="state" autocomplete="address-level4" /><br/>
    <label for="country">Country</label><input tabindex="-1" id="country" name="country" autocomplete="country" /><br/>

    <label for="birthday">Birthday</label><input tabindex="-1" id="birthday" name="birthday" autocomplete="bday" /><br/>
    <label for="language">Language</label><input tabindex="-1" id="language" name="language" autocomplete="language" /><br/>
    <label for="sex">Sex</label><input tabindex="-1" id="sex" name="sex" autocomplete="sex" /><br/>
    <label for="url">URL</label><input tabindex="-1" id="url" name="url" autocomplete="url" /><br/>
    <label for="photo">Photo</label><input tabindex="-1" id="photo" name="photo" autocomplete="photo" /><br/>
    <label for="impp">IMPP</label><input tabindex="-1" id="impp" name="impp" autocomplete="impp" /><br/>

    <label for="username">Username</label><input tabindex="-1" id="username" name="username" autocomplete="username" /><br/>
    <label for="password">Password</label><input tabindex="-1" id="password" name="password" autocomplete="password" /><br/>
    <label for="new-password">Password New</label><input tabindex="-1" id="new-password" name="new-password" autocomplete="new-password" /><br/>
    <label for="current-password">Password Current</label><input tabindex="-1" id="current-password" name="current-password" autocomplete="current-password" /><br/>

    <label for="cc">CC#</label><input tabindex="-1" id="cc" name="cc" autocomplete="cc-number" /><br/>
    <label for="cc-name">CC Name</label><input tabindex="-1" id="cc-name" name="cc-name" autocomplete="cc-name" /><br/>
    <label for="cc-expiration">CC expiration</label><input tabindex="-1" id="cc-expiration" name="cc-expiration" autocomplete="cc-expiration" /><br/>
    <label for="cc-zipcode">CC Zipcode</label><input tabindex="-1" id="cc-zipcode" name="cc-zipcode" autocomplete="cc-postalcode" /><br/>
  </div>
  <button>Submit</button>
</form>

Note: I created this demo a while back, and the standard is a living document. Since then, some of the autocomplete names have changed. For example, now we can specify new-password and current-password or more details for address or credit card that were not available before.

That form had three visible fields (name, email and zipcode). While that form is common among insurance companies, cable, and other service providers, it may not be too widespread, so I reduced the form even more with a single email field. We see that everywhere to sign up to websites, newsletters, or updates. You can see a running demo here:

A good thing: it displays all the data that will be shared as part of the form. Not only the data for the visible fields but all of them. At this point, the user may suspect something is not quite alright. There’s something fishy.

When I reduced the form to just the email field, Safari did something interesting. The autofill popup was different:

It states that it will only share the email (and it only does share that piece of information). But the contact info below may be trickier. When we click on that button, the browser shows a summary of the profile with its shared data. But that is not clearly stated anywhere. It simply looks like a regular contact card with some “share/do not share” options. After clicking on the “Autofill” button, the form is populated with all the data. Not only the email:

So there is a way for a user to share information with the form inadvertently. It’s tricky but not too far-fetched considering that it is the one “highlighted” with an icon out of the two possible options.

Funny thing, browsers separate the personal data from the credit card data, but Safari populated part of the credit card information based on the personal data (name and ZIP.)

Firefox

Using the autofill in Firefox is a bit more complex. It is not automatic like in Chrome, and there’s no icon like in Safari. Users will have to start typing or click a second time to see the autofill popup, which will have a note with every category that the browser will fill in, not only the visible fields:

Testing with the email-only form, Firefox presented the same autofill popup stating which fields categories it would populate. No difference whatsoever.

And similarly to the other browsers, after the autofill ran, we could read all the values with JavaScript.

Firefox was the best of the three: it clearly stated what information would be shared with the form independently of the fields or their order. And it hid the autofill functionality a second user interaction happened.

A keyboard user could select the autofill without realizing, by getting inside the popup bubble and pressing the tab key.

Chrome

Then it came the turn for Chrome. (Here I use “Chrome,” but the results were similar for several Chromium-based browsers tested.) I clicked on the field and, without any further interaction, the autofill popup showed. While Firefox and Safari had many things in common, Chrome is entirely different: it only shows two values, and both are visible.

This display was by design. I picked the order of the fields on purpose to get that particular combination of visible controls and autocomplete suggestions. However, it looks like Chrome gives some autocomplete properties more “weight” for the second value. And that makes the popup change depending on the order of the fields in the form.

Testing with the second version of the form was not much better:

While the popup shows a field that is not visible (the name), it is unclear what the purpose of the name is on the popup. An experienced user may know this happens because the name is shared, but an average user (and even the experienced ones) may think the email is associated with the profile with that name. There is zero indication of the data that the browser will share with the form.

And as soon as the user clicks on the autofill button, the data is available for the developer to read with JavaScript:

Chrome was the worst offender: it shared the information automatically, it was unclear what data was involved, and the autofill suggestions changed based on the controls’ order and attributes.

The first two issues are common to all/many browsers, to the point that it may even be considered a feature. However, the third issue is exclusive to Chromium browsers, and it facilitates a sketchy dark pattern.

This behavior would be more of an anecdote and not a problem if it wasn’t because Chrome takes a considerable market share of the browsers online (includes Chrome and Chromium-based).

The Dark Pattern

As you probably know, a dark pattern is a deceptive UX pattern that tricks users into doing things they may not really want to do.

“When you use websites and apps, you don’t read every word on every page — you skim read and make assumptions. If a company wants to trick you into doing something, they can take advantage of this by making a page look like it is saying one thing when it is in fact saying another.”

— Harry Brignull, darkpatterns.org

The behavior described in the previous points is clearly a deceptive user experience. Non-experienced users will not realize that they are sharing their personal data. Even more tech-savvy people may get tricked by it as Chrome makes it look like the selected option belongs to a profile instead of clearly stating what information is being shared.

The browser implementations cause this behavior, but it requires the developer to set it in place to exploit it. Unfortunately, there already are companies willing to exploit it, selling it as a feature to get leads.

As long as a dark pattern goes, it may also be an illegal one. This is because it breaks many principles relating to the processing of personal data specified in article 5 of the European General Data Protection Regulation (GDPR):

  • Lawfulness, fairness, and transparency
    The process is all but transparent.
  • Purpose limitation
    The data is processed in a way incompatible with the initial purpose.
  • Data minimization
    It is quite the opposite. Data maximization: get as much information as possible.

For example, if you want to sign up for a newsletter or request information about a product, and you provide your email, the website has no legal right to get your name, address, date of birth, phone number, or anything else without your consent or knowledge. Even if you considered that the user gave permission when clicking on the autofill, the purpose of the obtained data does not match the original intent of the form.

Possible Solutions

To avoid the problem, all actors need to contribute and help fix the issue:

  1. Users
  2. Developers and designers
  3. Browsers

1. Users

The only thing on the user side would be to ensure that the data displayed in the autofill popup is correct.

But we need to remember that the user is the victim here. We could blame them for not paying enough attention when clicking on the autofill, but that would be unfair. Plus, there are many reasons why a person could click on the button by mistake and share their data by accident. So even well-intentioned and savvy users may fall for it.

2. Developers And Designers

Let’s be honest. While the developers are not the root cause of the problem, they play a key role in exploiting the dark pattern. Either accidentally or with malicious intent.

And let’s be responsible and honest (this time in a literal way), because that’s the thing that developers and designers can do to build trust and make good use of the autofill and autocomplete features:

  • Only auto-complete the data that you need.
  • State clearly which data will be collected.
  • Do not hide form fields that will be later submitted.
  • Do not mislead or trick users into sending more data.

As an extreme measure, maybe try to avoid autocompleting certain fields. But, of course, this brings other issues as it will make the form less usable and accessible. So finding a balance may be tricky.

All this is without considering the possibility of an XSS vulnerability that could exploit the dark pattern. Of course, that would be a completely different story and an even more significant problem.

3. Browsers

Much of the work would need to be done from the browser side (especially on the Chromium side). But let me start by stating that not all is bad with how web browsers handle autofill/autocomplete. Many things are good. For example:

  • They limit the data that can be shared
    Browsers have a list of fields for auto-complete that may not include all the values described in the HTML standard.
  • They encapsulate and group data
    Browsers separate personal and financial information to protect critical values like credit cards. Safari had some issues with this, but it was minor.
  • They warn about the data that will be shared
    Sometimes this may be incomplete (Chrome) or not clear (Safari), but they do alert the user.

Still, some things can be improved by many or all of the web browsers.

Show All Fields That Will Be Autocompleted

Browsers should always show a list of all the fields that will be autocompleted within the autofill popup (instead of just a partial list.) Also, the information should be clearly identified as data to be shared instead of being displayed as a regular contact card that could be misleading.

Firefox did an excellent job at this point, Safari did a nice job in general, and Chrome was subpar compared to the other two.

Do Not Trigger The onChange Event On Autofill

This would be a problematic request because this behavior is part of the Autofill definition in the HTML standard:

“The autocompletion mechanism must be implemented by the user agent acting as if the user had modified the control’s data [...].”

This means that browsers should treat the autocompleted data as if it had been entered by the user, thus triggering all the events, showing the values, etc. Even on a non-visually available field.

Preventing this behavior on non-visible elements could solve the problem. But validating if a form control is visible or not could be costly for the browser. Also, this solution is only partial because developers could still read the values even without the inputs triggering events.

Do Not Allow Devs To Read Autocompleted Fields Before Submission

This would also be problematic because many developers often rely on reading the field values before submission to validate the values (e.g., when the user moves away from the inputs.) But it would make sense: the user doesn’t want to share the information until they submit the form, so the browser shouldn’t either.

An alternative to this would be providing fake data when reading autocompleted values. Web browsers already do something like this with visited links, why not do the same with autocompleted form fields? Provide gibberish as the name, a valid address that matches local authorities instead of the user’s address, a fake phone number? This could solve the developer validation needs while protecting the user’s personal information.

Displaying a complete list of the fields/values that the browser will clearly share with the form would be a great step forward. The other two are ideal but more of stretch goals. Still, they are initiatives that would considerably improve privacy.

Would the autofill dark pattern still be possible to exploit? Unfortunately, yes. But it would be a lot more complicated. And at this point, it would be the user’s responsibility and the developer’s duty to avoid such a situation.

Conclusion

We can argue that autocomplete is not a huge security issue (not even on Chrome) because it requires user interaction to select the information. However, we could also argue that the potential loss of data justifies proper action. And Chrome has done more changes for (relatively) less important security/usability concerns (see alert(), prompt(), and confirm() than what could be done to protect key personal information.

Then we have the issue of the dark pattern. It can be avoided if everyone does their part:

  • Users should be careful with what forms/data they autofill;
  • Developers should avoid exploiting that data;
  • Browsers should do a better job at protecting people’s data.

At the root, this dark pattern is a browser issue (and mainly a Chrome issue), and not a small one (privacy should be key online). But there is a choice. In the end, exploiting the dark pattern or not is up to the developers. So let’s pick wisely and do the right thing.

Further Reading On Smashing Magazine

HTML is Not a Programming Language?

HTML is not a programming language.

I’ve heard that sentence so many times and it’s tiring. Normally, it is followed by something like, It doesn’t have logic, or, It is not Turing complete,.so… obviously it is not a programming language. Like it’s case-closed and should be the end of the conversation.

Should it be, though?

I want to look at typical arguments I hear used to belittle HTML and offer my own rebuttals to show how those claims are not completely correct.

My goal is not to prove that HTML is or is not a programming language, but to show that the three main arguments used for claiming it is not are flawed or incorrect, thus invalidating the conclusion from a logical point of view.

“HTML is a markup language, not a programming language”

This statement, by itself, sounds great… but it is wrong: markup languages can be programming languages. Not all of them are (most are not) but they can be. If we drew a Venn diagram of programming languages and markup languages, it would not be two separate circles, but two circles that slightly intersect:

A markup language that operates with variables, has control structures, loops, etc., would also be a programming language. They are not mutually exclusive concepts.

TeX and LaTeX are examples of markup languages that are also considered programming languages. It may not be practical to develop with them, but it is possible. And we can find examples online, like a BASIC interpreter or a Mars Rover controller (which won the Judges’ prize in the ICFP 2008 programming contest).

While some markup languages might be considered programming languages, I’m not saying that HTML is one of them. The point is that the original statement is wrong: markup languages can be programming languages. Therefore, saying that HTML is not a programming language because it is a markup language is based on a false statement, and whatever conclusion you arrive at from that premise will be categorically wrong.

“HTML doesn’t have logic”

This claim demands that we clarify what “logic” means because the definition might just surprise you.

As with Turing-completeness (which we’ll definitely get to), those who bring this argument to the table seem to misunderstand what it is exactly. I’ve asked people to tell me what they mean by “logic” and have gotten interesting answers back like:

Logic is a sensible reason or way of thinking.

That’s nice if what we’re looking for is a dictionary definition of logic. But we are talking about programming logic, not just logic as a general term. I’ve also received answers like:

Programming languages have variables, conditions, loops, etc. HTML is not a programming language because you can’t use variables or conditions. It has no logic.

This is fine (and definitely better than getting into true/false/AND/OR/etc.), but also incorrect. HTML does have variables — in the form of attributes — and there are control structures that can be used along with those variables/attributes to determine what is displayed.

But how do you control those variables? You need JavaScript!

Wrong again. There are some HTML elements that have internal control logic and don’t require JavaScript or CSS to work. And I’m not talking about things like <link> or <noscript> – which are rudimentary control structures and have been part of the standard for decades. I’m referring to elements that will respond to the user input and perform conditional actions depending on the current state of the element and the value of a variable. Take the <details>/<summary> tuple or the <dialog> element as examples: when a user clicks on them, they will close if the open attribute is present, and they will open if it is not. No JavaScript required.

So just saying alone that HTML isn’t a programming language because it lacks logic is misleading. We know that HTML is indeed capable of making decisions based on user input. HTML has logic, but it is inherently different from the logic of other languages that are designed to manipulate data. We’re going to need a stronger argument than that to prove that HTML isn’t a form of programming.

“HTML is not ‘Turing complete’”

OK, this is the one we see most often in this debate. It’s technically correct (the best kind of correct) to say HTML is not Turing complete, but it should spark a bigger debate than just using it as a case-closing statement.

I’m not going to get into the weeds on what it means to be Turing complete because there are plenty of resources on the topic. In fact, Lara Schenck summarizes it nicely in a post where she argues that CSS is Turning complete:

In the simplest terms, for a language or machine to be Turing complete, it means that it is capable of doing what a Turing machine could do: perform any calculation, a.k.a. universal computation. After all, programming was invented to do math although we do a lot more with it now, of course!

Because most modern programming languages are Turing complete, people use that as the definition of a programming language. But Turing-completeness is not that. It is a criterion to identify if a system (or its ruleset) can simulate a Turing machine. It can be used to classify programming languages; it doesn’t define them. It doesn’t even apply exclusively to programming languages. Take, for example, the game Minecraft (which meets that criterion) or the card game Magic: The Gathering (which also meets the criterion). Both are Turing complete but I doubt anyone would classify them as programming languages.

Turing-completeness is fashionable right now the same way that some in the past considered the difference between compiled vs. interpreted languages to be good criteria. Yes. We don’t have to make a big memory effort to remember when developers (mainly back-end) downplayed front-end programming (including JavaScript and PHP) as not “real programming.” You still hear it sometimes, although now faded, mumbled, and muttered.

The definition of what programming is (or is not) changes with time. I bet someone sorting through punched cards complained about how typing code in assembly was not real programming. There’s nothing universal or written in stone. There’s no actual definition.

Turing-completeness is a fair standard, I must say, but one that is biased and subjective — not in its form but in the way it is picked. Why is it that a language capable of generating a Turing Complete Machine gets riveted as a “programming language” while another capable of generating a Finite State Machine is not? It is subjective. It is an excuse like any other to differentiate between “real developers” (the ones making the claim) and those inferior to them.

To add insult to injury, it is obvious that many of the people parroting the “HTML is not Turing complete” mantra don’t even know or understand what Turing-completeness means. It is not an award or a seal of quality. It is not a badge of honor. It is just a way to categorize programming languages — to group them, not define them. A programming language could be Turing complete or not in the same way that it could be interpreted or compiled, imperative or declarative, procedural or object-oriented.


So, is HTML a programming language?

If we can debase the main arguments claiming that HTML is not a programming language, does that actually mean that HTML is a programming language? No, it doesn’t. And so, the debate will live on until the HTML standard evolves or the “current definition” of programming language changes.

But as developers, we must be wary of this question as, in many cases, it is not used to spark a serious debate but to stir controversy while hiding ulterior motives: from getting easy Internet reactions, to dangerously diminishing the contribution of a group of people to the development ecosystem.

Or, as Ashley Kolodziej beautifully sums it up in her ode to HTML:

They say you’re not a real programming language like the others, that you’re just markup, and technically speaking, I suppose that’s right. Technically speaking, JavaScript and PHP are scripting languages. I remember when it wasn’t cool to know JavaScript, when it wasn’t a “real” language too. Sometimes, I feel like these distinctions are meaningless, like we built a vocabulary to hold you (and by extension, ourselves as developers) back. You, as a markup language, have your own unique value and strengths. Knowing how to work with you best is a true expertise, one that is too often overlooked.

Independent of the stance that we take on the “HTML is/isn’t a programming language” discussion, let’s celebrate it and not deny its importance: HTML is the backbone of the Internet. It’s a beautiful language with vast documentation and extensive syntax, yet so simple that it can be learned in an afternoon, and so complex that it takes years to master. Programming language or not, what really matters is that we have HTML in the first place.


The post HTML is Not a Programming Language? appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

Playing Sounds with CSS

CSS is the domain of styling, layout, and presentation. It is full of colors, sizes, and animations. But did you know that it could also control when a sound plays on a web page?

This article is about a little trick to pull that off. It’s actually a strict implementation of the HTML and CSS, so it’s not really a hack. But… let’s be honest, it’s still kind of a hack. I wouldn’t recommend necessarily using it in production, where audio should probably be controlled with native <audio> elements and/or JavaScript.

The trick

There are a few alternatives to playing sounds with CSS, but the underlying idea is the same: inserting the audio file as a hidden object/document within the web page, and displaying it whenever an action happens. Something like this:

<style>
  embed { display: none; }
  button:active + embed { display: block; }
</style>

<button>Play Sound</button>
<embed src="path-to-audio-file.mp3" />

View Demo

This code uses an <embed> tag, but we could also use <object> with similar results:

<object data="path-to-audio-file.mp3"></object>

A quick note on the demo and this technique. I developed a small piano on CodePen just with HTML and CSS using this technique about a year ago. It worked great, but since then, some things have changed and the demo doesn’t work on CodePen anymore.

The biggest change was related to security. As it uses embed or object instead of audio, the imported file is subject to stricter security checks. Cross-origin access control policies (CORS) force the audio file to be on the same protocol and domain as the page it is imported into. Even putting the sound in base64 will not work anymore. Also, you (and users) may need to activate autoplay on their browser settings for this trick to work. It is often enabled behind a flag.

Another change is that browsers now only play the sounds once. I could have sworn that past browsers played the sound every time that it was shown. But that doesn’t appear to be the case anymore, which considerably limits the scope of the trick (and bares the piano demo almost useless).

The CORS issue can be worked around if you have control over the servers and files, but the disabled autoplay is a per-user thing that is out of our control.

View Demo

Why it works

The theory behind this behavior can be found buried in the definition of the embed tag:

Whenever an embed element that was not potentially active becomes potentially active, and whenever a potentially active embed element that is remaining potentially active and has its src attribute set, changed, or removed or its type attribute set, changed, or removed, the user agent must queue a task using the embed task source to run the embed element setup steps for that element.

Same goes for the definition of the object tag:

Whenever one of the following conditions occur:

[...]

  • the element changes from being rendered to not being rendered, or vice versa,

[...] the user agent must queue a task to run the following steps to (re)determine what the object element represents. [and eventually process and run it]

While it is clearer for object (the file is processed and run on render), we have this concept of “potentially active” for embed that may seem a bit more complicated. And while there are some additional conditions, it will run on initial render similarly as how it does with object.

As you can see, technically this is not a trick at all, but how all browsers should behave... although they don’t.

Browser support

As with many of these hacks and tricks, the support of this feature is not great and varies considerably from browser to browser.

It works like a charm on Opera and Chrome, which means a big percentage of the browser market. However, the support is spotty for other Chromium-based browsers. For example, Edge on Mac will play the audio correctly, while the Brave browser won't unless you have the latest version.

Safari was a non-starter, and the same can be said for Internet Explorer or Edge on Windows. Nothing close to working on any of these browsers.

Firefox will play all the sounds at once on page load, but then won’t play them after they are hidden and shown again. It will trigger a security warning in the console as the sounds attempt to be play “without user interaction,” blocking them unless the user approves the site first.

Overall, this is a fun trick with CSS but one of those “don’t do this at home” kind of things… which in software development, means this is the perfect thing to do at home. 🙂

The post Playing Sounds with CSS appeared first on CSS-Tricks.

Are There Random Numbers in CSS?

CSS allows you to create dynamic layouts and interfaces on the web, but as a language, it is static: once a value is set, it cannot be changed. The idea of randomness is off the table. Generating random numbers at runtime is the territory of JavaScript, not so much CSS. Or is it? If we factor in a little user interaction, we actually can generate some degree of randomness in CSS. Let’s take a look!

Randomization from other languages

There are ways to get some "dynamic randomization" using CSS variables as Robin Rendle explains in an article on CSS-Tricks. But these solutions are not 100% CSS, as they require JavaScript to update the CSS variable with the new random value.

We can use preprocessors such as Sass or Less to generate random values, but once the CSS code is compiled and exported, the values are fixed and the randomness is lost. As Jake Albaugh explains:

Why do I care about random values in CSS?

In the past, I've developed simple CSS-only apps such as a trivia game, a Simon game, and a magic trick. But I wanted to do something a little bit more complicated. I'll leave a discussion about the validity, utility, or practicality of creating these CSS-only snippets for a later time.

Based on the premise that some board games could be represented as Finite State Machines (FSM), they could be represented using HTML and CSS. So I started developing a game of Snakes and Ladders (aka Chutes and Ladders). It is a simple game. The goal is to advance a pawn from the beginning to the end of the board by avoiding the snakes and trying to go up the ladders.

The project seemed feasible, but there was something that I was missing: rolling dice!

The roll of dice (along with the flip of a coin) are universally recognized for randomization. You roll the dice or flip the coin, and you get an unknown value each time.

Simulating a random dice roll

I was going to superimpose layers with labels, and use CSS animations to "rotate" and exchange which layer was on top. Something like this:

Simulation of how the layers animate on a browser

The code to mimic this randomization is not excessively complicated and can be achieved with an animation and different animation delays:

/* The highest z-index is the numbers of sides in the dice */ 
@keyframes changeOrder {
  from { z-index: 6; } 
  to { z-index: 1; } 
} 

/* All the labels overlap by using absolute positioning */ 
label { 
  animation: changeOrder 3s infinite linear;
  background: #ddd;
  cursor: pointer;
  display: block;
  left: 1rem;
  padding: 1rem;
  position: absolute;
  top: 1rem; 
  user-select: none;
} 
    
/* Negative delay so all parts of the animation are in motion */ 
label:nth-of-type(1) { animation-delay: -0.0s; } 
label:nth-of-type(2) { animation-delay: -0.5s; } 
label:nth-of-type(3) { animation-delay: -1.0s; } 
label:nth-of-type(4) { animation-delay: -1.5s; } 
label:nth-of-type(5) { animation-delay: -2.0s; } 
label:nth-of-type(6) { animation-delay: -2.5s; }

The animation has been slowed down to allow easier interaction (but still fast enough to see the roadblock explained below). The pseudo-randomness is clearer, too.

See the Pen
Demo of pseudo-randomly generated number with CSS
by Alvaro Montoro (@alvaromontoro)
on CodePen.

But then I hit a roadblock: I was getting random numbers, but sometimes, even when I was clicking on my "dice," it was not returning any value.

I tried increasing the times in the animation, and that seemed to help a bit, but I was still having some unexpected values.

That's when I did what most developers do when they find a roadblock they cannot resolve just by searching online: I asked other developers for help in the form of a StackOverflow question.

Luckily for me, the always resourceful Temani Afif came up with an explanation and a solution.

To simplify a little, the problem was that the browser only triggers the click/press event when the element that is active on mouse down is the same element that is active on mouse up.

Because of the rotating animation, the top label on mouse down was not the top label on mouse up, unless I did it fast or slow enough for the animation to circle around. That's why increasing the animation times hid these issues.

The solution was to apply a position of "static" to break the stacking context, and use a pseudo-element like ::before or ::after with a higher z-index to occupy its place. This way, the active label would always be on top when the mouse went up.

/* The active tag will be static and moved out of the window */ 
label:active {
  margin-left: 200%;
  position: static;
}

/* A pseudo-element of the label occupies all the space with a higher z-index */
label:active::before {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  bottom: 0;
  z-index: 10;
}

Here is the code with the solution with a faster animation time:

See the Pen
Demo of pseudo-randomly generated number with CSS
by Alvaro Montoro (@alvaromontoro)
on CodePen.

After making this change, the one thing left was to create a small interface to draw a fake dice to click, and the CSS Snakes and Ladders was completed.

This technique has some obvious inconveniences

  • It requires user input: a label must be clicked to trigger the "random number generation."
  • It doesn't scale well: it works great with small sets of values, but it is a pain for large ranges.
  • It’s not really random, but pseudo-random: a computer could easily detect which value would be generated in each moment.

But on the other hand, it is 100% CSS (no need for preprocessors or other external helpers) and, for a human user, it can look 100% random.

And talking about hands... This method can be used not only for random numbers but for random anything. In this case, we used it to "randomly" pick the computer choice in a Rock-Paper-Scissors game:

See the Pen
CSS Rock-Paper-Scissors
by Alvaro Montoro (@alvaromontoro)
on CodePen.

The post Are There Random Numbers in CSS? appeared first on CSS-Tricks.