Web Standards

Dark mode and variable fonts

Css Tricks - Thu, 04/23/2020 - 11:43am

Not so long ago, we wrote about dark mode in CSS and I’ve been thinking about how white text on a black background is pretty much always harder to read than black text on a white background. After thinking about this for a while, I realized that we can fix that problem by making the text thinner in dark mode using variable fonts!

Here’s an example of the problem where I’m using the typeface Yanone Kaffeesatz from Google Fonts. Notice that the section with white text on a black background looks heavier than the section with black text on a white background.

CodePen Embed Fallback

Oddly enough, these two bits of text are actually using the same font-weight value of 400. But to my eye, the white text looks extra bold on a black background.

Stare at this example for a while. This is just how white text looks on a darker background; it’s how our eyes perceive shapes and color. And this might not be a big issue in some cases but reading light text on a dark background is always way more difficult for readers. And if we don’t take care designing text in a dark mode context, then it can feel as if the text is vibrating as we read it.

How do we fix this?

Well, this is where variable fonts come in! We can use a lighter font weight to make the text easier to read whenever dark mode is active:

body { font-weight: 400; } @media (prefers-color-scheme: dark) { body { font-weight: 350; } }

Here’s how that looks with this new example:

CodePen Embed Fallback

This is better! The two variants now look a lot more balanced to me.

Again, it’s only a small difference, but all great designs consist of micro adjustments like this. And I reckon that, if you’re already using variable fonts and loading all these weights, then you should definitely adjust the text so it’s easier to read.

This effect is easier to spot if we compare the differences between longer paragraphs of text. Here we go, this time in Literata:

CodePen Embed Fallback

Notice that the text on the right feels bolder, but it just isn’t. It’s simply an optical allusion — both examples above have a font-weight of 500.

So to fix this issue we can do the same as the example above:

body { font-weight: 500; } @media (prefers-color-scheme: dark) { body { font-weight: 400; } } CodePen Embed Fallback

Again, it’s a slight change but it’s important because at these sizes every typographic improvement we make helps the reading experience.

Oh and here’s a quick Google fonts tip!

Google Fonts lets you add a font to your website by adding a <link> in the <head> of the document, like this:

<head> <link href="https://fonts.googleapis.com/css2?family=Rosario:wght@515&display=swap" rel="stylesheet"> </head>

That’s using the Rosario typeface and adding a font-weight of 515 — that’s the bit in the code above that says wght@515. Even if this happens to be a variable font, only this font weight will be downloaded. If we try to do something like this though…

body { font-weight: 400; }

…nothing will happen! In fact, the font won’t load at all. Instead, we need to declare which range of font-weight values we want by doing the following:

<link href="https://fonts.googleapis.com/css2?family=Yanone+Kaffeesatz:wght@300..500&display=swap" rel="stylesheet">

This @300..500 bit in the code above is what tells Google Fonts to download a font file with all the weights between 300 and 500. Alternatively, adding a ; between each weight will then only download weights 300 and 500 – so, for example, you can’t pick weight 301:

<link href="https://fonts.googleapis.com/css2?family=Yanone+Kaffeesatz:wght@300;500&display=swap" rel="stylesheet">

It took me a few minutes to figure out what was going wrong and why the font wasn’t loading at all, so hopefully the Google Fonts team can make that a bit clearer with the embed codes in the future. Perhaps there should be an option or a toggle somewhere to select a range or specific weights (or maybe I just didn’t see it).

Either way, I think all this is why variable fonts can be so gosh darn helpful; they allow us to adjust text in ways that we’ve never been able to do before. So, yay for variable fonts!

The post Dark mode and variable fonts appeared first on CSS-Tricks.

Innovating on Web Monetization: Coil and Firefox Reality

Css Tricks - Thu, 04/23/2020 - 4:56am

I still think Coil is cool. I have it installed on CSS-Tricks as a publisher and money trickles in. I have a paid account and I trickle out money to other sites that use it. I wrote about all that last year.

This’ll explode to something huge if we actually get the Web Monetization API stuff. No more browser extensions would be needed, and a real ecosystem could be built around it.

Anselm Hook writes about using Coil (for now) to monetize games on the web, which is a good reminder that this isn’t just for publications — it’s for anything-web. Coil even works for things off your own domain, like your YouTube channel.

Direct Link to ArticlePermalink

The post Innovating on Web Monetization: Coil and Firefox Reality appeared first on CSS-Tricks.

Rethinking Twitter as a Serverless App

Css Tricks - Thu, 04/23/2020 - 4:56am

In a previous article, we showed how to build a GraphQL API with FaunaDB. We’ve also written a series of articles [1, 2, 3, 4] explaining how traditional databases built for global scalability have to adopt eventual (vs. strong) consistency, and/or make compromises on relations and indexing possibilities. FaunaDB is different since it does not make these compromises. It’s built to scale so it can safely serve your future startup no matter how big it gets, without sacrificing relations and consistent data.

In this article, we’re very excited to start bringing all of this together in a real-world app with highly dynamic data in a serverless fashion using React hooks, FaunaDB, and Cloudinary. We will use the Fauna Query Language (FQL) instead of GraphQL and start with a frontend-only approach that directly accesses the serverless database FaunaDB for data storage, authentication, and authorization.

The golden standard for example applications that feature a specific technology is a todo app–mainly because they are simple. Any database out there can serve a very simple application and shine. 

And that is exactly why this app will be different! If we truly want to show how FaunaDB excels for real world applications, then we need to build something more advanced. 

Introducing Fwitter

When we started at Twitter, databases were bad. When we left, they were still bad

Evan Weaver

Since FaunaDB was developed by ex-Twitter engineers who experienced these limitations first-hand, a Twitter-like application felt like an appropriately sentimental choice. And, since we are building it with FaunaDB, let’s call this serverless baby ‘Fwitter’

Below is a short video that shows how it looks, and the full source code is available on GitHub.

When you clone the repo and start digging around, you might notice a plethora of well-commented example queries not covered in this article. That’s because we’ll be using Fwitter as our go-to example application in future articles, and building additional features into it with time. 

But, for now, here’s a basic rundown of what we’ll cover here:

We build these features without having to configure operations or set up servers for your database. Since both Cloudinary and FaunaDB are scalable and distributed out-of-the-box, we will never have to worry about setting up servers in multiple regions to achieve low latencies for users in other countries. 

Let’s dive in!

Modeling the data 

Before we can show how FaunaDB excels at relations, we need to cover the types of relations in our application’s data model. FaunaDB’s data entities are stored in documents, which are then stored in collections–like rows in tables. For example, each user’s details will be represented by a User document stored in a Users collection. And we eventually plan to support both single sign-on and password-based login methods for a single user, each of which will be represented as an Account document in an Accounts collection.

At this point, one user has one account, so it doesn’t matter which entity stores the reference (i.e., the user ID). We could have stored the user ID in either the Account or the User document in a one-to-one relation:


However, since one User will eventually have multiple Accounts (or authentication methods), we’ll have a one-to-many model.


In a one-to-many relation between Users and Accounts, each Account points to only one user, so it makes sense to store the User reference on the Account:

We also have many-to-many relations, like the relations between Fweets and Users, because of the complex ways users interact with each other via likes, comments, and refweets. 


Further, we will use a third collection, Fweetstats, to store information about the interaction between a User and a Fweet.

Fweetstats’ data will help us determine, for example, whether or not to color the icons indicating to  the user that he has already liked, commented, or refweeted a Fweet. It also helps us determine what clicking on the heart means: unlike or like.

The final model for the application will look like this: 

The application model of the fwitter application

Fweets are the center of the model, because they contain the most important data of the Fweet such as the information about the message, the number of likes, refweets, comments, and the Cloudinary media that was attached. FaunaDB stores this data in a json format that looks like this: 

As shown in the model and in this example json, hashtags are stored as a list of references. If we wanted to, we could have stored the complete hashtag json in here, and that is the preferred solution in more limited document-based databases that lack relations. However, that would mean that our hashtags would be duplicated everywhere (as they are in more limited databases) and it would be more difficult to search for hashtags and/or retrieve Fweets for a specific hashtag as shown below.

Note that a Fweet does not contain a link to Comments, but the Comments collection contains a reference to the Fweet. That’s because one Comment belongs to one Fweet, but a Fweet can have many comments–similar to the one-to-many relation between Users and Accounts.

Finally, there is a FollowerStats collection which basically saves information about how much users interact with each other in order to personalize their respective feeds. We won’t cover that much in this article, but you can experiment with the queries in the source code and stay tuned for a future article on advanced indexing.

Hopefully, you’re starting to see why we chose something more complex than a ToDo app. Although Fwitter  is nowhere near the complexity of the real Twitter app on which it’s based, it’s already becoming apparent that implementing such an application without relations would be a serious brainbreaker. 

Now, if you haven’t already done so from the github repo, it’s finally time to get our project running locally!

Setup the project

To set up the project, go to the FaunaDB dashboard and sign up. Once you are in the dashboard, click on New Database, fill in a name, and click Save. You should now be on the “Overview” page of your new database. 

Next, we need a key that we will use in our setup scripts. Click on the Security tab in the left sidebar, then click the New key button. 

In the “New key” form, the current database should already be selected. For “Role”, leave it as “Admin”. Optionally, add a key name. Next, click Save and copy the key secret displayed on the next page. It will not be displayed again.

Now that you have your database secret, clone the git repository and follow the readme. We have prepared a few scripts so that you only have to run the following commands to initialize your app, create all collections, and populate your database. The scripts will give you further instructions:

// install node modules npm install // run setup, this will create all the resources in your database // provide the admin key when the script asks for it. // !!! the setup script will give you another key, this is a key // with almost no permissions that you need to place in your .env.local as the // script suggestions npm run setup npm run populate // start the frontend

After the script, your .env.local file should contain the bootstrap key that the script provided you (not the admin key)


You can optionally create an account with Cloudinary and add your cloudname and a public template (there is a default template called ‘ml_default’ which you can make public) to the environment to include images and videos in the fweets. 


Without these variables, the include media button will not work, but the rest of the app should run fine:

Creating the front end

For the frontend, we used Create React App to generate an application, then divided the application into pages and components. Pages are top-level components which have their own URLs. The Login and Register pages speak for themselves. Home is the standard feed of Fweets from the authors we follow; this is the page that we see when we log into our account. And the User and Tag pages show the Fweets for a specific user or tag in reverse chronological order. 

We use React Router to direct to these pages depending on the URL, as you can see in the src/app.js file.

<Router> <SessionProvider value={{ state, dispatch }}> <Layout> <Switch> <Route exact path="/accounts/login"> <Login /> </Route> <Route exact path="/accounts/register"> <Register /> </Route> <Route path="/users/:authorHandle" component={User} /> <Route path="/tags/:tag" component={Tag} /> <Route path="/"> <Home /> </Route> </Switch> </Layout> </SessionProvider> </Router>

The only other thing to note in the above snippet is the SessionProvider, which is a React context to store the user’s information upon login. We’ll revisit this in the authentication section. For now, it’s enough to know that this gives us access to the Account (and thus User) information from each component.

Take a quick look at the home page (src/pages/home.js) to see how we use a combination of hooks to manage our data. The bulk of our application’s logic is implemented in FaunaDB queries which live in the src/fauna/queries folder. All calls to the database pass through the query-manager, which in a future article, we’ll refactor into serverless function calls. But for now these calls originate from the frontend and we’ll secure the sensitive parts of it with FaunaDB’s ABAC security rules and User Defined Functions (UDF). Since FaunaDB behaves as a token-secured API, we do not have to worry about a limit on the amount of connections as we would in traditional databases. 

The FaunaDB JavaScript driver

Next, take a look at the src/fauna/query-manager.js file to see how we connect FaunaDB to our application using FaunaDB’s JavaScript driver, which is just a node module we pulled with `npm install`. As with any node module, we import it into our application as so:

import faunadb from 'faunadb'

And create a client by providing a token. 

this.client = new faunadb.Client({ secret: token || this.bootstrapToken })

We’ll cover tokens a little more in the Authentication section. For now, let’s create some data! 

Creating data

The logic to create a new Fweet document can be found in the src/fauna/queries/fweets.js file. FaunaDB documents are just like JSON, and each Fweet follows the same basic structure: 

const data = { data: { message: message, likes: 0, refweets: 0, comments: 0, created: Now() } }

The Now() function is used to insert the time of the query so that the Fweets in a user’s feed can be sorted chronologically. Note that FaunaDB automatically places timestamps on every database entity for temporal querying. However, the FaunaDB timestamp represents the time the document was last updated, not the time it was created, and the document gets updated every time a Fweet is liked; for our intended sorting order, we need the created time. 

Next, we send this data to FaunaDB with the Create() function. By providing Create() with the reference to the Fweets collection using Collection(‘fweets’), we specify where the data needs to go. 

const query = Create(Collection('fweets'), data )

We can now wrap this query in a function that takes a message parameter and executes it using client.query() which will send the query to the database. Only when we call client.query() will the query be sent to the database and executed. Before that, we combine as many FQL functions as we want to construct our query. 

function createFweet(message, hashtags) { const data = … const query = … return client.query(query) }

Note that we have used plain old JavaScript variables to compose this query and in essence just called functions. Writing FQL is all about function composition; you construct queries by combining small functions into larger expressions. This functional approach has very strong advantages. It allows us to use native language features such as JavaScript variables to compose queries, while also writing higher-order FQL functions that are protected from injection.

For example, in the query below, we add hashtags to the document with a CreateHashtags() function that we’ve defined elsewhere using FQL.

const data = { data: { // ... hashtags: CreateHashtags(tags), likes: 0, // ... }

The way FQL works from within the driver’s host language (in this case, JavaScript) is what makes FQL an eDSL (embedded domain-specific language). Functions like CreateHashtags() behave just like a native FQL function in that they are both just functions that take input. This means that we can easily extend the language with our own functions, like in this open source FQL library from the Fauna community. 

It’s also important to notice that we create two entities in two different collections, in one transaction. Thus, if/when things go wrong, there is no risk that the Fweet is created yet the Hashtags are not. In more technical terms, FaunaDB is transactional and consistent whether you run queries over multiple collections or not, a property that is rare in scalable distributed databases. 

Next, we need to add the author to the query. First, we can use the Identity() FQL function to return a reference to the currently logged in document. As discussed previously in the data modeling section, that document is of the type Account and is separated from Users to support SSO in a later phase.

Then, we need to wrap Identity() in a Get() to access the full Account document and not just the reference to it.


Finally, we wrap all of that in a Select() to select the data.user field from the account document and add it to the data JSON. 

const data = { data: { // ... hashtags: CreateHashtags(tags), author: Select(['data', 'user'], Get(Identity())), likes: 0, // ... } }

Now that we’ve constructed the query, let’s pull it all together and call client.query(query) to execute it.

function createFweet(message, hashtags) { const data = { data: { message: message, likes: 0, refweets: 0, comments: 0, author: Select(['data', 'user'], Get(Identity())), hashtags: CreateHashtags(tags), created: Now() } } const query = Create(Collection('fweets'), data ) return client.query(query) }

By using functional composition, you can easily combine all your advanced logic in one query that will be executed in one transaction. Check out the file src/fauna/queries/fweets.js to see the final result which takes even more advantage of function composition to add rate-limiting, etc. 

Securing your data with UDFs and ABAC roles

The attentive reader will have some thoughts about security by now. We are essentially creating queries in JavaScript and calling these queries from the frontend. What stops a malicious user from altering these queries? 

FaunaDB provides two features that allow us to secure our data: Attribute-Based Access Control (ABAC) and User Defined Functions (UDF). With ABAC, we can control which collections or entities that a specific key or token can access by writing Roles. 

With UDFs, we can push FQL statements to the database by using the CreateFunction(). 

CreateFunction({ name: 'create_fweet', body: <your FQL statement>, })

Once the function is in the database as a UDF, where the application can’t alter it anymore, we then call this UDF from the front end.

client.query( Call(Function('create_fweet'), message, hashTags) )

Since the query is now saved on the database (just like a stored procedure), the user can no longer manipulate it. 

One example of how UDFs can be used to secure a call is that we do not pass in the author of the Fweet. The author of the Fweet is derived from the Identity() function instead, which makes it impossible for a user to write a Fweet on someone’s behalf.

Of course, we still have to define that the user has access to call the UDF. For that, we will use a very simple ABAC role that defines a group of role members and their privileges. This role will be named logged_in_role, its membership will include all of the documents in the Accounts collection, and all of these members will be granted the privilege of calling the create_fweet UDF.

CreateRole( name: 'logged_in_role', privileges: [ { resource: q.Function('create_fweet'), actions: { call: true } } ], membership: [{ resource: Collection('accounts') }], )

We now know that these privileges are granted to an account, but how do we ‘become’ an Account? By using the FaunaDB Login() function to authenticate our users as explained in the next section.

How to implement authentication in FaunaDB

We just showed a role that gives Accounts the permissions to call the create_fweets function. But how do we “become” an Account?.

First, we create a new Account document, storing credentials alongside any other data associated with the Account (in this case, the email address and the reference to the User).

return Create(Collection('accounts'), { credentials: { password: password }, data: { email: email, user: Select(['ref'], Var('user')) } }) }

We can then call Login() on the Account reference, which retrieves a token.

Login( Match( < Account reference > , { password: password } ) )

We use this token in the client to impersonate the Account. Since all Accounts are members of the Account collection, this token fulfills the membership requirement of the logged_in_role and is granted access to call the create_fweet UDF.

To bootstrap this whole process, we have two very important roles.

  • bootstrap_role: can only call the login and register UDFs
  • logged_in_role: can call other functions such as create_fweet

The token you received when you ran the setup script is essentially a key created with the bootstrap_role. A client is created with that token in src/fauna/query-manager.js which will only be able to register or login. Once we log in, we use the new token returned from Login() to create a new FaunaDB client which now grants access to other UDF functions such as create_fweet. Logging out means we just revert to the bootstrap token. You can see this process in the src/fauna/query-manager.js, along with more complex role examples in the src/fauna/setup/roles.js file. 

How to implement the session in React

Previously, in the “Creating the front end” section, we mentioned the SessionProvider component. In React, providers belong to a React Context which is a concept to facilitate data sharing between different components. This is ideal for data such as user information that you need everywhere in your application. By inserting the SessionProvider in the HTML early on, we made sure that each component would have access to it. Now, the only thing a component has to do to access the user details is import the context and use React’s ‘useContext’ hook.

import SessionContext from '../context/session' import React, { useContext } from 'react' // In your component const sessionContext = useContext(SessionContext) const { user } = sessionContext.state

But how does the user end up in the context? When we included the SessionProvider, we passed in a value consisting of the current state and a dispatch function. 

const [state, dispatch] = React.useReducer(sessionReducer, { user: null }) // ... <SessionProvider value={{ state, dispatch }}>

The state is simply the current state, and the dispatch function is called to modify the context. This dispatch function is actually the core of the context since creating a context only involves calling React.createContext() which will give you access to a Provider and a Consumer.

const SessionContext = React.createContext({}) export const SessionProvider = SessionContext.Provider export const SessionConsumer = SessionContext.Consumer export default SessionContext

We can see that the state and dispatch are extracted from something that React calls a reducer (using React.useReducer), so let’s write a reducer.

export const sessionReducer = (state, action) => { switch (action.type) { case 'login': { return { user: action.data.user } } case 'register': { return { user: action.data.user } } case 'logout': { return { user: null } } default: { throw new Error(`Unhandled action type: ${action.type}`) } } }

This is the logic that allows you to change the context. In essence, it receives an action and decides how to modify the context given that action. In my case, the action is simply a type with a string. We use this context to keep user information, which means that we call it on a successful login with: 

sessionContext.dispatch({ type: 'login', data: e }) Adding Cloudinary for media 

When we created a Fweet, we did not take into account assets yet. FaunaDB is meant to store application data, not image blobs or video data. However, we can easily store the media on Cloudinary and just keep a link in FaunaDB. The following inserts the Cloudinary script (in app.js):


We then create a Cloudinary Upload Widget (in src/components/uploader.js):

window.cloudinary.createUploadWidget( { cloudName: process.env.REACT_APP_LOCAL___CLOUDINARY_CLOUDNAME, uploadPreset: process.env.REACT_APP_LOCAL___CLOUDINARY_TEMPLATE, }, (error, result) => { // ... } )

As mentioned earlier, you need to provide a Cloudinary cloud name and template in the environment variables (.env.local file) to use this feature. Creating a Cloudinary account is free and once you have an account you can grab the cloud name from the dashboard.

You have the option to use API keys as well to secure uploads. In this case, we upload straight from the front end so the upload uses a public template. To add a template or modify it to make it public, click on the gear icon in the top menu, go to Upload tab, and click Add upload preset

You could also edit the ml_default template and just make it public.

Now, we just call widget.open() when our media button is clicked.

const handleUploadClick = () => { widget.open() } return ( <div> <FontAwesomeIcon icon={faImage} onClick={handleUploadClick}></FontAwesomeIcon> </div> )

This provides us with a small media button that will open the Cloudinary Upload Widget when it’s clicked. 

When we create the widget, we can also provide styles and fonts to give it the look and feel of our own application as we did above (in src/components/uploader.js): 

const widget = window.cloudinary.createUploadWidget( { cloudName: process.env.REACT_APP_LOCAL___CLOUDINARY_CLOUDNAME, uploadPreset: process.env.REACT_APP_LOCAL___CLOUDINARY_TEMPLATE, styles: { palette: { window: '#E5E8EB', windowBorder: '#4A4A4A', tabIcon: '#000000', // ... }, fonts: {

Once we have uploaded media to Cloudinary, we receive a bunch of information about the uploaded media, which we then add to the data when we create a Fweet.

We can then simply use the stored id (which Cloudinary refers to as the publicId) with the Cloudinary React library (in src/components/asset.js):

import { Image, Video, Transformation } from 'cloudinary-react'

To show the image in our feed.

<div className="fweet-asset"> <Image publicId={asset.id} cloudName={cloudName} fetchFormat="auto" quality="auto" secure="true" /> </div>

When you use the id, instead of the direct URL, Cloudinary does a whole range of optimizations to deliver the media in the most optimal format possible. For example when you add a video image as follows:

<div className="fweet-asset"> <Video playsInline autoPlay loop={true} controls={true} cloudName={cloudName} publicId={publicId}> <Transformation width="600" fetchFormat="auto" crop="scale" /> </Video> </div>

Cloudinary will automatically scale down the video to a width of 600 pixels and deliver it as a WebM (VP9) to Chrome browsers (482 KB), an MP4 (HEVC) to Safari browsers (520 KB), or an MP4 (H.264) to browsers that support neither format (821 KB). Cloudinary does these optimizations server-side, significantly improving page load time and the overall user experience. 

Retrieving data

We have shown how to add data. Now we still need to retrieve data. Getting the data of our Fwitter feed has many challenges. We need to: 

  • Get fweets from people you follow in a specific order (taking time and popularity into account)
  • Get the author of the fweet to show his profile image and handle
  • Get the statistics to show how many likes, refweets and comments it has
  • Get the comments to list those beneath the fweet. 
  • Get info about whether you already liked, refweeted, or commented on this specific fweet. 
  • If it’s a refweet, get the original fweet. 

This kind of query fetches data from many different collections and requires advanced indexing/sorting, but let’s start off simple. How do we get the Fweets? We start off by getting a reference to the Fweets collection using the Collection() function.


And we wrap that in the Documents() function to get all of the collection’s document references.


We then Paginate over these references.


Paginate() requires some explanation. Before calling Paginate(), we had a query that returned a hypothetical set of data. Paginate() actually materializes that data into pages of entities that we can read. FaunaDB requires that we use this Paginate() function to protect us from writing inefficient queries that retrieve every document from a collection, because in a database built for massive scale, that collection could contain millions of documents. Without the safeguard of Paginate(), that could get very expensive!

Let’s save this partial query in a plain JavaScript variable references that we can continue to build on.

const references = Paginate(Documents(Collection('fweets')))

So far, our query only returns a list of references to our Fweets. To get the actual documents, we do exactly what we would do in JavaScript: map over the list with an anonymous function. In FQL, a Lambda is just an anonymous function.

const fweets = Map( references, Lambda(['ref'], Get(Var('ref'))) )

This might seem verbose if you’re used to declarative query languages like SQL that declare what you want and let the database figure out how to get it. In contrast, FQL declares both what you want and how you want it which makes it more procedural. Since you’re the one defining how you want your data, and not the query engine, the price and performance impact of your query is predictable. You can exactly determine how many reads this query costs without executing it, which is a significant advantage if your database contains a huge amount of data and is pay-as-you-go. So there might be a learning curve, but it’s well worth it in the money and hassle it will save you. And once you learn how FQL works, you will find that queries read just like regular code. 

Let’s prepare our query to be extended easily by introducing Let. Let will allow us to bind variables and reuse them immediately in the next variable binding, which allows you to structure your query more elegantly.

const fweets = Map( references, Lambda( ['ref'], Let( { fweet: Get(Var('ref')) }, // Just return the fweet for now Var('fweet') ) ) )

Now that we have this structure, getting extra data is easy. So let’s get the author.

const fweets = Map( references, Lambda( ['ref'], Let( { fweet: Get(Var('ref')), author: Get(Select(['data', 'author'], Var('fweet'))) }, { fweet: Var('fweet'), author: Var('user') } ) ) )

Although we did not write a join, we have just joined Users (the author) with the Fweets. We’ll expand on these building blocks even further in a follow up article. Meanwhile, browse src/fauna/queries/fweets.js to view the final query and several more examples. 

More in the code base

If you haven’t already, please open the code base for this Fwitter example app. You will find a plethora of well-commented examples we haven’t explored here, but will in future articles. This section touches on a few files we think you should check out.

First, check out the src/fauna/queries/fweets.js file for examples of how to do complex matching and sorting with FaunaDB’s indexes (the indexes are created in src/fauna/setup/fweets.js). We implemented three different access patterns to get Fweets by popularity and time, by handle, and by tag.

Getting Fweets by popularity and time is a particularly interesting access pattern because it actually sorts the Fweets by a sort of decaying popularity based on users’ interactions with each other. 

Also, check out src/fauna/queries/search.js, where we’ve implemented autocomplete based on FaunaDB indexes and index bindings to search for authors and tags. Since FaunaDB can index over multiple collections, we can write one index that supports an autocomplete type of search on both Users and Tags.

We’ve implemented these examples because the combination of flexible and powerful indexes with relations is rare for scalable distributed databases. Databases that lack relations and flexible indexes require you to know in advance how your data will be accessed and you will run into problems when your business logic needs to change to accommodate your clients’ evolving use cases.

In FaunaDB, if you did not foresee a specific way that you’d like to access your data, no worries — just add an Index! We have range indexes, term indexes, and composite indexes that can be specified whenever you want without having to code around eventual consistency. 

A preview of what’s to come 

As mentioned in the introduction, we’re introducing this Fwitter app to demonstrate complex, real-world use cases. That said, a few features are still missing and will be covered in future articles, including streaming, pagination, benchmarks, and a more advanced security model with short-lived tokens, JWT tokens, single sign-on (possibly using a service like Auth0), IP-based rate limiting (with Cloudflare workers), e-mail verification (with a service like SendGrid), and HttpOnly cookies.

The end result will be a stack that relies on services and serverless functions which is very similar to a dynamic JAMstack app, minus the static site generator. Stay tuned for the follow-up articles and make sure to subscribe to the Fauna blog and monitor CSS-Tricks for more FaunaDB-related articles. 

The post Rethinking Twitter as a Serverless App appeared first on CSS-Tricks.

My Visual Studio Code Setup: Extensions and Themes

Css Tricks - Wed, 04/22/2020 - 10:19am

Matthias Ott’s posted his VS Code setup. I find lists like this (I rounded up some recent updates of my own) irresistible, probably because, like y’all, I spend an awful lot of time in VS Code and wanna make sure I’m getting the most out of it.

Things from the list that stood out to me:

  • I didn’t realize Bracket Pair Colorizer had gone v2 and it’s a separate install.
  • I didn’t realize you needed an extension to honor .editorconfig files.
  • I wasn’t using anything for PHP, but Matthias listed PHP Intelephense and I’m giving it a whirl. It has fewer users than the non-weirdly named one though? And when I installed that, I saw Format HTML in PHP which I’m also trying because, yes, please! (Even Prettier’s PHP add-on can’t do that.)

Messing with extensions is also a good opportunity to clear out old crap.

Also super interesting…

So, https://t.co/7DZWDzAsTb is currently an email series (you can sign up now to catch up).

It's turning into an ebook/screencast/extra-stuff product.

I'm gonna cover A TON, but I think the PHP section will be one of the most valuable parts for most of you pic.twitter.com/sdVK4L1m4n

— Caleb Porzio (@calebporzio) April 20, 2020

The main point of that series is cleaning up the interface of VS Code in extreme ways. All the way down to:

Direct Link to ArticlePermalink

The post My Visual Studio Code Setup: Extensions and Themes appeared first on CSS-Tricks.

Puzzles and Mysteries

Css Tricks - Wed, 04/22/2020 - 4:54am

Bob Hoffman:

Puzzles, [Malcom Gladwell] wrote, are problems for which there is not enough information. An example of a puzzle: Where is Jimmy Hoffa buried? If we had more information, we would know the answer. If someone told us “Jimmy Hoffa is buried in New Jersey,” we’d know a little more than we know now. If they said, “He’s buried in northern New Jersey,” we’d know even more. If they said, “He’s buried in the Meadowlands,” we’d have an answer to our puzzle.

On the other hand, there are mysteries. Mysteries are problems for which we have plenty of information, but no accurate analysis. An example of a mystery: Why do inner-city schools do such a crappy job of educating kids? There are thousands of studies. Every education department of every university in America has done a study on this; every committee of Congress has done a report on it; every editorial writer has a theory about it, and every pundit has an opinion. And yet, we have no definitive answer.

It’s fun to think about how that correlates to front-end development. When we’re coding, every¹ problem we face is a puzzle. We just need more information and we can figure out what to do. Sometimes, design is like that too. Information can make our designs better. But success in design² has a nebulous quality that makes it feel more like a mystery.

  1. Except for CORS. CORS is a mystery.
  2. And business, marketing, and love.

Direct Link to ArticlePermalink

The post Puzzles and Mysteries appeared first on CSS-Tricks.

Fake Code

Css Tricks - Tue, 04/21/2020 - 11:01am

Here’s a fun little idea from Knut Synstad. You give it the URL of a GitHub Gist and it converts the Gist into grayscale rounded blobs (SVG) that sorta look like code if you squint. Maybe fun for interesting dynamic backgrounds or for whatever you might use code-looking stock art for.

It reminded me of Christian Naths’s Redacted font, which turns every glyph into a box or squiggles.

And if you need some actual totally fake code, Harry Parton’s Pen is nice for that:

CodePen Embed Fallback

The post Fake Code appeared first on CSS-Tricks.

Constrained CSS grids without `max-width`

Css Tricks - Tue, 04/21/2020 - 4:43am

Ain’t nothing wrong with max-width, but Ethan makes a point in the last sentence:

Rather than simply defaulting to max-width as a constraint, I can use the empty space around my design, and treat it as a layout tool.

If the space “around” your grid is actually part of the grid, it’s easier to use. Maybe you’d decide to scootch some author information up there after all, or show an ad, or who knows what. It will be more robust if you do it within the established grid.

Direct Link to ArticlePermalink

The post Constrained CSS grids without `max-width` appeared first on CSS-Tricks.

Drupal to Jamstack

Css Tricks - Tue, 04/21/2020 - 3:04am

I’ve been harping for a while that Jamstack doesn’t necessarily mean throwing away your old CMS. In fact, I’d argue that Jamstack is at it’s most powerful when paired with a system that you already know, are comfortable with, and perhaps even like. You’d call that decoupling the front end.

Netlify has a webinar coming up on exactly this, featuring Alex De Winne, who is going to show you a real-world site in a live demo combining Drupal (a classic PHP & MySQL CMS that powers an absolute ton of sites) and Jamstack architecture.

Register for Free Webinar

The post Drupal to Jamstack appeared first on CSS-Tricks.

Can JavaScript Detect the Browser’s Zoom Level?

Css Tricks - Mon, 04/20/2020 - 1:08pm

No, not really.

My first guess was that this was intentionally not exposed in browsers because browsers intentionally don’t want us fighting it — or making well-intentioned but bad-outcome decisions based on that info. But I don’t see any evidence of that.

StackOverflow answers paint how weird cross-browser it can be. This script from 2013 works for me in Chrome, but not at all in Safari and reports incorrectly in Firefox. Even if that script worked, it relies on user agent detection (which is not long for this world) and some incredibly weird hacks.

So please, feel free to correct me if I’m wrong, but I think the answer is that we can’t really do this right now.

There is a thing called the Visual Viewport API

I’m kinda confused by it.

  • The spec is a draft
  • The support chart lists a lot of support
  • window.visualViewport is defined in Firefox, Safari, and Chrome (desktop)
  • But… window.visualViewport.scale is always 1 in Safari and Chrome, and undefined in Firefox. In other words: useless.

I don’t entirely know if that is supposed to accurately represent the browser zoom level, because the spec talks about specifically about pinch-zoom. So, maybe it’s just not intended for desktop browser zoom levels.

What’s a use case here?

I had a fella spell out a situation like this:

He wanted to use CSS grid to layout cemetery plots (interesting already), like a top-down blueprint of a graveyard. There was lots of information in the layout. If you were “zoomed out” so you could see the whole graveyard on one page, the text in each area would be too small to read (sincethe type would be sized to fit within the boxes/graves). Ideally, the page would hide that text while the browser is zoomed out (perhaps a .hide-text class). When zoomed in far enough, the text is shown again.


// Dunno if "resize" is best. I don't know what the "change zoom" event would be window.visualViewport.addEventListener("resize", viewportHandler); function viewportHandler(event) { // NOTE: This doesn't actually work at time of writing if (event.target.scale > 3) { document.body.classList.remove("hide-text"); } else { document.body.classList.add("hide-text"); } } There is Pixel Density…

Ben Nadel recently blogged: Looking At How Browser Zoom Affects CSS Media Queries And Pixel-Density.

If you look at window.devicePixelRatio and zoom in, the pixel density in Chrome and Firefox will increase as you zoom in and decrease as you zoom out. Theoretically, you could test the original value (it might start at different places for users with different screens) and use changes in that value to guess zoom. But… in Safari it does nothing, as in, it stays the same regardless of zoom. Plus, the operating system zoom level can affect things here, making it extra tricky; not to mention that a page might start at a zoomed in level which could throw off the whole calculation from the start.

The post Can JavaScript Detect the Browser’s Zoom Level? appeared first on CSS-Tricks.

The Contrast Triangle

Css Tricks - Mon, 04/20/2020 - 12:15pm

Chip Cullen:

Let’s say you’re building a site, and you’re working with a designer. They come to you with some solid designs, and you’re ready to go. You’re also a conscientious front end developer and you like to make sure the sites you build are accessible. The designs you’re working from have some body copy, but you notice that the links inside the body copy are missing underlines.

You are now in The Contrast Triangle.

The “triangle” meaning a three-sided comparison:

  • The contrast between the background and text
  • The contrast between the background and links
  • the contrast between text and links

I would think this matters whether you underline links or not, but I take the point that without underlines the problem is worse.

I’d also argue the problem is even more complicated. You’ve also got to consider the text when it is selected, and make sure everything has contrast when that is the case too. And the hover states, active states, visited states, and focus states. So it’s really like hexacontakaienneagon of contrast or something.

Direct Link to ArticlePermalink

The post The Contrast Triangle appeared first on CSS-Tricks.

How (some) good corporate engineering blogs are written

Css Tricks - Sat, 04/18/2020 - 10:23am

Interesting research from Dan Luu¹:

… it’s pretty common for my personal blog to get more traffic than the entire corp eng blog for a company with a nine to ten figure valuation and it’s not uncommon for my blog to get an order of magnitude more traffic.

I think this is odd because tech companies in that class often have hundreds to thousands of employees. They’re overwhelmingly likely to be better equipped to write a compelling blog than I am and companies get a lot more value from having a compelling blog than I do.

First, yes. There is a crapload of value in having a good blog (top of funnel traffic, showcasing culture for hiring, establishing industry leadership…) yet so few companies do it well even when they have more than enough resources to do so.

Dan doesn’t just speculate on this, he interviewed people at companies that have actually good engineering blogs: Heap, Segment, and Cloudflare. Then he listed their internal process for blogging. The first step in all three is the same: “Someone has an idea to write a post”. That makes sense, but I would think there is something deeper going on with good blogs: engineers that want to come up with ideas because it is encouraged and incentivized. And then after the ball is rolling, there is a positive feedback loop and as few blockers as possible.

Random observations from me:

  • We recently started using Appcues at CodePen, and it was on our radar at all because people at CodePen read their blog and liked it.
  • While Appcues largely blogs about stuff that is directly related to stuff their software can help with, Logrocket, a software product for tracking JavaScript errors, has a blog that isn’t terribly different than CSS-Tricks. It’s just about front-end everything and every single blog post has a section in it that is a pitch for the product. Looks like they’ve been doing it for about 3 years now, so my hunch is that it works extremely well.
  • All the browser vendors do a pretty good job of blogging overall, but at the same time, feel a bit disjointed. What blog(s) should I be reading for Mozilla/Firefox stuff? I don’t know really. Is it the official looking one? Or the “hacks” one? Or the planet one? Or nightly one? Or the one for releases? Google seems to have the same problem. There isn’t an obvious hub of writing.
  • Stripe has a strong engineering blog, but they take it to another level by producing a fancy publication (Increment) that is both online and in print.
  1. A little CSS would go a long way on this site: body { max-width: 600px; margin: 20px auto; }

Direct Link to ArticlePermalink

The post How (some) good corporate engineering blogs are written appeared first on CSS-Tricks.

Front-End Challenges

Css Tricks - Fri, 04/17/2020 - 11:50am

My favorite way to level up as a front-end developer is to do the work. Literally just build websites. If you can do it for money, great, you should. If the websites you make can help yourself or anyone else you care about, then that’s also great. In lieu of that, you can also make things simply for the sake of making them, and you’ll still level up. It’s certainly better than just reading about things!

Here’s some resources that encourage you to level up by building things for the sake of leveling up, if you’re up to it.

Frontend Mentor

It looks like this recently launched, and it’s what inspired this post. This idea of giving people front-end work to do is enough to build a business around! Some of them are free, and some of them are not.


Other businesses have centered themselves around this too. HackerRank is all about getting jobs and hiring, so they’ve got a very strong agenda, but part of the way they do that is putting you through these skill tests (solving challenges) which are meant to asses you, but you can certainly learn from them too.

Others like this: Codewars, ChallengeRocket, Codesignal, Topcoder (Jeepers, VCs must love this idea.)


Coderbyte has paid plans too, and they’re designed to leveling up your job interviewing skills with challenges.

Classic situation: sometimes the site is the product and you’re the customer, and sometimes hiring companies are the customer and you’re the product.

Build Dribbble shots

Here’s the classic move: find something you like on Dribbble, rebuild it. The @keyframers often do it. Tim Evko’s Practice site used to pick a shot for you (along with random GitHub issues and random coding challenges), but the Dribbble part appears broken at the moment. The other stuff still works!

Matt Delac used to do a series along these lines. Indrek Lasn also does it in Medium posts.

Front-End Challenges Club

Andy Bell did a Front-End Challenges Club for a while, and while I think it’s on break, you can view the archives.

CodePen Challenges

CodePen Challenges run every week are are a prompt (along with ideas and resources) for building whatever you like. Low key.

100 Days of CSS Challenge

Matthias Martin created 100 days of CSS challenges. They are all there for you to see, including entries from other people — but the point is for you to give it a shot yourself, of course.

Daily UI

Daily UI challenges gives you a new challenge every day that starts when you sign up (and it’s free). Lots of people complete the challenge with code.


Frontloops charges $19 for 30 challenges, which includes information, advice, assets, and a solution.


If your idea of a fun challenge is mimicking a design in as few bytes of code as possible, CSSBattle will appeal to you.

Writing things as tersely as possible is often called “Code Golf” and there is a challenge site for that too.

Ace Front End

Ace Front End has challenges that focus specifically on vanilla HTML, CSS, and JavaScript.

I just happened to notice that the first challenge is a drop down navigation menu, and it doesn’t handle things like aria-expanded. I’m not entirely sure how big of a problem that is and I don’t mean to pick on Ace Front End — it’s just a reminder that there could be problems with any of these challenges. But that doesn’t mean you can’t learn something from them.


Codier has public challenges that include solutions posted by other users.

rendezvous with cassidoo

Cassidy’s weekly newsletter includes a challenge in every issue.

Rina Diane Caballar quoting Tim Carry in Extending the Limits of CSS:

Carry’s advice is to start with a real-world object—the interface of a gaming console or a calculator, for example—and to try to recreate it using only CSS. “A great way to push the boundaries with a language is to make something that the language wasn’t meant to be doing in the first place,” he says.

The post Front-End Challenges appeared first on CSS-Tricks.

Pseudo-Randomly Adding Illustrations with CSS

Css Tricks - Fri, 04/17/2020 - 10:28am

Between each post of Eric Meyer’s blog there’s this rather lovely illustration that can randomly be one of these five options:

Eric made each illustration into a separate background image then switches out that image with the nth-of-type CSS property, like this:

.entry:nth-of-type(2n+1)::before { background-image: url(image-1.png); } .entry:nth-of-type(3n+1)::before { background-image: url(image-2.png); } .entry:nth-of-type(4n+1)::before { background-image: url(image-3.png); } .entry:nth-of-type(5n+1)::before { background-image: url(image-4.png); }

This seems like a good time to plug our very own little :nth Tester tool. It definitely helps me understand what something like (2n+1) means in English. You can type in any string you like and see what effect that has on your site:

Anyway, back to Eric’s post. As he mentions, his technique is pseudo-random in that it looks like a random image on the page but it technically isn’t. Either way, I think it’s a really lovely technique! And it certainly breaks up the visual monotony that happens when you’re looking at a website for too long.

Here’s what it looks like in practice:

Lovely stuff!

Another way to do this is to use random numbers in CSS. For example, we could set a variable in JavaScript and then apply it with CSS custom properties. Or we could put all the images in a single sprite file and change the background-position based on a random number.

This is definitely one of those things in CSS where there are no wrong answers; just different ways to do the same awesome thing!

Direct Link to ArticlePermalink

The post Pseudo-Randomly Adding Illustrations with CSS appeared first on CSS-Tricks.

Better Form Inputs for Better Mobile User Experiences

Css Tricks - Fri, 04/17/2020 - 5:08am

Here’s one simple, practical way to make apps perform better on mobile devices: always configure HTML input fields with the correct type, inputmode, and autocomplete attributes. While these three attributes are often discussed in isolation, they make the most sense in the context of mobile user experience when you think of them as a team. 

There’s no question that forms on mobile devices can be time-consuming and tedious to fill in, but by properly configuring inputs, we can ensure that the data entry process is as seamless as possible for our users. Let’s take a look at some examples and best practices we can use to create better user experiences on mobile devices.

Use this demo to experiment on your own, if you’d like. Using the correct input type

This is the easiest thing to get right. Input types, like email, tel, and url, are well-supported across browsers. While the benefit of using a type, like tel over the more generic text, might be hard to see on desktop browsers, it’s immediately apparent on mobile.

Choosing the appropriate type changes the keyboard that pops up on Android and iOS devices when a user focuses the field. For very little effort, just by using the right type, we will show custom keyboards for email, telephone numbers, URLs, and even search inputs

Text input type on iOS (left) and Android (right) Email input type on iOS (left) and Android (right) URL input type on iOS (left) and Android (right) Search input type on iOS (left) and Android (right)

One thing to note is that both input type="email" and input type="url" come with validation functionality, and modern browsers will show an error tooltip if their values do not match the expected formats when the user submits the form. If you’d rather turn this functionality off, you can simply add the novalidate attribute to the containing form.

A quick detour into date types

HTML inputs comprise far more than specialized text inputs — you also have radio buttons, checkboxes, and so on. For the purposes of this discussion, though, I’m mostly talking about the more text-based inputs

There is a type of input that sits in the liminal space between the more free-form text inputs and input widgets like radio buttons: date. The date input type comes in a variety of flavors that are well-supported on mobile, including date, time, datetime-local, and month. These pop up custom widgets in iOS and Android when they are focused. Instead of triggering a specialized keyboard, they show a select-like interface in iOS, and various different types of widgets on Android (where the date and time selectors are particularly slick). 

I was excited to start using native defaults on mobile, until I looked around and realized that most major apps and mobile websites use custom date pickers rather than native date input types. There could be a couple reasons for this. First, I find the native iOS date selector to be less intuitive than a calendar-type widget. Second, even the beautifully-designed Android implementation is fairly limited compared to custom components — there’s no easy way to input a date range rather than a single date, for instance. 

Still, the date input types are worth checking out if the custom datepicker you’re using doesn’t perform well on mobile. If you’d like to try out the native input widgets on iOS and Android while making sure that desktop users see a custom widget instead of the default dropdown, this snippet of CSS will hide the calendar dropdown for desktop browsers that implement it:

::-webkit-calendar-picker-indicator {   display: none; } Date input type on iOS (left) and Android (right) Time input type on iOS (left) and Android (right)

One final thing to note is that date types cannot be overridden by the inputmode attribute, which we’ll discuss next.

Why should I care about inputmode?

The inputmode attribute allows you to override the mobile keyboard specified by the input’s type and directly declare the type of keyboard shown to the user. When I first learned about this attribute, I wasn’t impressed — why not just use the correct type in the first place? But while inputmode is often unnecessary, there are a few places where the attribute can be extremely helpful. The most notable use case that I’ve found for inputmode is building a better number input.

While some HTML5 input types, like url and email, are straightforward, input type="number" is a different matter. It has some accessibility concerns as well as a somewhat awkward UI. For example, desktop browsers, like Chrome, show tiny increment arrows that are easy to trigger accidentally by scrolling.

So here’s a pattern to memorize and use going forwards. For most numeric inputs, instead of using this: 

<input type="number" />

…you actually want to use this:

<input type="text" inputmode="decimal" />

Why not inputmode="numeric" instead of inputmode="decimal" ? 

The numeric and decimal attribute values produce identical keyboards on Android. On iOS, however, numeric displays a keyboard that shows both numbers and punctuation, while decimal shows a focused grid of numbers that almost looks exactly like the tel input type, only without extraneous telephone-number focused options. That’s why it’s my preference for most types of number inputs.

iOS numeric input (left) and decimal input (right) Android numeric input (left) and decimal input (right)

Christian Oliff has written an excellent article dedicated solely to the inputmode attribute.

Don’t forget autocomplete

Even more important than showing the correct mobile keyboard is showing helpful autocomplete suggestions. That can go a long way towards creating a faster and less frustrating user experience on mobile.

While browsers have heuristics for showing autocomplete fields, you cannot rely on them, and should still be sure to add the correct autocomplete attribute. For instance, in iOS Safari, I found that an input type="tel" would only show autocomplete options if I explicitly added a autocomplete="tel" attribute.

You may think that you are familiar with the basic autocomplete options, such as those that help the user fill in credit card numbers or address form fields, but I’d urge you to review them to make sure that you are aware of all of the options. The spec lists over 50 values! Did you know that autocomplete="one-time-code" can make a phone verification user flow super smooth?

Speaking of autocomplete…

I’d like to mention one final element that allows you to create your own custom autocomplete functionality: datalist. While it creates a serviceable — if somewhat basic — autocomplete experience on desktop Chrome and Safari, it shines on iOS by surfacing suggestions in a convenient row right above the keyboard, where the system autocomplete functionality usually lives. Further, it allows the user to toggle between text and select-style inputs.

On Android, on the other hand, datalist creates a more typical autocomplete dropdown, with the area above the keyboard reserved for the system’s own typeahead functionality. One possible advantage to this style is that the dropdown list is easily scrollable, creating immediate access to all possible options as soon as the field is focused. (In iOS, in order to view more than the top three matches, the user would have to trigger the select picker by pressing the down arrow icon.)

You can use this demo to play around with datalist:

CodePen Embed Fallback

And you can explore all the autocomplete options, as well as input type and inputmode values, using this tool I made to help you quickly preview various input configurations on mobile.

In summary

When I’m building a form, I’m often tempted to focus on perfecting the desktop experience while treating the mobile web as an afterthought. But while it does take a little extra work to ensure forms work well on mobile, it doesn’t have to be too difficult. Hopefully, this article has shown that with a few easy steps, you can make forms much more convenient for your users on mobile devices.

The post Better Form Inputs for Better Mobile User Experiences appeared first on CSS-Tricks.

How to Make Your Graphic Design Go Viral

Usability Geek - Thu, 04/16/2020 - 5:25pm
The advancement in web technology has enhanced the significance and usage of Graphic design. Now it is not limited to specific things.  There was a time when graphic designing was limited to...
Categories: Web Standards

Thank You, Christopher Schmitt

Css Tricks - Thu, 04/16/2020 - 10:15am

It’s incredibly sad that Christopher Schmitt passed away last week¹. I keep thinking about how Christopher was one of the best dudes I knew. Just incredibly kind and thoughtful all the way through. I know everyone says that about people after they pass, but I really mean it here. I’m sure we all know people that we like quite a bit, but hey, we know they can be an asshole sometimes too. Not Christopher, at least not to me. He was always good to me in ways I’ll never be able to reciprocate.

Here’s the most important thing:

We (a group of developer friends) would like to build a site of memories that people have of Christopher. We have our own, but we’d like to collect as many memories from as many people as possible to make this a true memorial to him. If you have one you would like to share publicly, please submit it to this little site just for that.

I hardly know where to start reminiscing about Christopher. Broadly: I spoke at more conferences thrown by Christopher (and Ari) than anyone else. At least a dozen. He used to throw one called In Control in Orlando that I spoke at a number of times and met some early web friends that I’m still close with today! Stephanie Rewis mentioned to me that’s where we met the first time and she remembers me coding away on a yet-to-be released CodePen while there. In Control went to Hawaii one year, along with the first-ever CSS Dev Conf, bringing me to Hawaii for the first time in my life, where I remember driving around the island with Daniel Burka. CSS Dev Conf went all sorts of amazing places, and I went to all of them! Estes Park, New Orleans, The Queen Mary… all these incredible locations that were all incredible memories for me.

And all I have are blurry, distant photos of him like this:

Christopher and Ari also ran an absolute ton of online conferences. CSS Summit, RWD Summit, SVG Summit… they ran a ton of them and they were really ahead of their time. I remember a funny moment where I had just moved to a new city and my internet wasn’t installed yet, but I still had to present online at one of these, so I did it out of my neighbors garage (as their kids slept), and the light in the garage kept going out because it didn’t detect movement. &#x1f62c;

The people I met through Christopher are too many to count. He was a great connector in that way. His influence on the web was tremendous through his direct work, like writing (what, a dozen books?), blogging (even here!), speaking, and podcasting (e.g. The Non Breaking Space Show) but when you consider the people he brought together, his influence is immeasurable.

I literally made money off the guy. In the early years of ShopTalk, when it was tough to sell podcast spots at all, Christopher would sponsor every episode of ShopTalk, which probably gave us the boost that ensured it’s still happening today. Who knows what would have happened without that.

One of the things I liked best about Christopher is that he was a damn nerd. Through and through. In a way that made you envious that you could one day hope to up your nerd level that high. Guy had a Chewbacca costume that he wore regularly. His Instagram is full of snaps of him meeting other famous nerds.

I hope you still got those black Converses on wherever you are, buddy.

(And remember, if you have a memory with Christopher, please share it.)

  1. It wasn’t COVID-19. Poor guy was dealt bad cards from day one for his health and he fought it the the best he could.

The post Thank You, Christopher Schmitt appeared first on CSS-Tricks.

Creating Color Themes With Custom Properties, HSL, and a Little calc()

Css Tricks - Thu, 04/16/2020 - 10:01am

Before the advent of CSS custom properties (we might call them “variables” in this article as that’s the spirit of them), implementing multiple color schemes on the same website usually meant writing separate stylesheets. Definitely not the most maintainable thing in the world. Nowadays, though, we can define variables in a single stylesheet and let CSS do the magic.

Even if you aren’t offering something like user-generated or user-chosen color themes, you might still use the concept of theming on your website. For example, it is fairly common to use different colors themes across different areas of the site.

We’re going to build out an example like this:

Same layout, different colors.

In this example, all that changes between sections is the color hue; the variations in lightness are always the same. Here’s an example of a simplified color palette for a specific hue:

CodePen Embed Fallback

A palette of multiple hues might look something like this:

CodePen Embed Fallback

This would take effort to do with RGB color value, but in HSL only one value changes.

Enter custom properties

Custom properties have been around for a while and are widely supported. Polyfills and other solutions for IE 11 are also available.

The syntax is very similar to traditional CSS. Here is an overview of the basic usage:

CodePen Embed Fallback

It’s common to see variables defined on the :root pseudo-element, which is always <html> in HTML, but with higher specificity. That said, variables can be defined on any element which is useful for scoping specific variables to specific elements. For example, here are variables defined on data attributes:

CodePen Embed Fallback Adding calc() to the mix

Variables don’t have to be fixed values. We can leverage the power of the calc() function to automatically calculate values for us while adhering to a uniform pattern:

CodePen Embed Fallback

Since CSS doesn’t support loops, a preprocessor would be handy to generate a part of the code. But remember: CSS variables are not the same as Sass variables.

Implementing CSS variables

What we’re basically trying to do is change the color of the same component on different sections of the same page. Like this:

CodePen Embed Fallback

We have three sections in tabs with their own IDs: #food, #lifestyle, and #travel. Each section corresponds to a different hue. The  data-theme-attribute on the div.wrapper element defines which hue is currently in use.

When #travel is the active tab, we’re using the --first-hue variable, which has a value of 180°. That is what gets used as the --hue value on the section, resulting in a teal color:

<div class="wrapper" data-theme="travel"> .wrapper[data-theme="travel"] {   --hue: var(--first-hue);  /* = 180° = teal */ }

Clicking any of the tabs updates the data-theme attribute to the ID of the section, while removing the hash (#) from it. This takes a smidge of JavaScript. That’s one of the (many) nice things about CSS: they can be accessed and manipulated with JavaScript. This is a far cry from preprocessor variables, which compile into values at the build stage and are no longer accessible in the DOM.

<li><a href="#food">Food</a></li> const wrapper = document.querySelector('.wrapper'); document.querySelector("nav").addEventListener('click', e => {   e.preventDefault();   e.stopPropagation();   // Get theme name from URL and ditch the hash   wrapper.dataset.theme = e.target.getAttribute('href').substr(1); }) Progressive enhancement

When we use JavaScript, we should be mindful of scenarios where a user may have disabled it. Otherwise, our scripts — and our UI by extension — are inaccessible. This snippet ensures that the site content is still accessible, even in those situations:

document.querySelectorAll('section').forEach((section, i) => {   if (i) { // hide all but the first section     section.style.display = 'none';   } })

This merely allows the tabs to scroll up the page to the corresponding section. Sure, theming is gone, but providing content is much more important.

While I chose to go with a single-page approach, it’s also possible to serve the sections as separate pages and set [data-theme] on the server side. 

Another approach

So far, we’ve assumed that color values change linearly and are thus subject to a mathematical approach. But even in situations where this is only partially true, we may still be able to benefit from the same concept. For instance, if lightness follows a pattern but hue doesn’t, we could split up the stylesheet like this:

<head>   <style>     :root {       --hue: 260;     }   </style>   <link rel="stylesheet" href="stylesheet-with-calculations-based-on-any-hue.css"> </head> Supporting web components

Web components are an exciting (and evolving) concept. It’s enticing to think we can have encapsulated components that can be reused anywhere and theme them on a case-by-case basis. One component with many contexts!

We can use CSS variable theming with web components. It requires us to use a host-context() pseudo-selector. (Thanks to habemuscode for pointing this out to me!)

:host-context(body[data-theme="color-1"]) {   --shade-1: var(--outsideHSL); } In summary…

Theming a website with CSS custom properties is much easier than the workaround approaches we’ve resorted to in the past. It’s more maintainable (one stylesheet), performant (less code), and opens up new possibilities (using JavaScript). Not to mention, CSS custom properties become even more powerful when they’re used with HSL colors and the calc() function.

We just looked at one example where we can change the color theme of a component based on the section where it is used. But again, there is much more opportunity here when we start to get into things like letting users change themes themselves – a topic that Chris explores in this article.

The post Creating Color Themes With Custom Properties, HSL, and a Little calc() appeared first on CSS-Tricks.

Jetpack Instant Search!

Css Tricks - Thu, 04/16/2020 - 9:19am

Jetpack has had a search feature for a while. Flip it on, and it replaces your built-in WordPress search (which is functional, but not particularly good) with an Elasticsearch-powered solution that is faster and has better results. I’ve been using that for quite a while here on CSS-Tricks, which was an upgrade from using a Google Custom Search Engine.

Jetpack just upped their game again with a brand new search upgrade. You can use Jetpack search how you were already, or you can flip on Instant Search and take advantage of this all-new search experience (inside and out).

Flipping on Jetpack Instant Search from Jetpack Settings A Full Page Experience

Instant Search provides a full page-covering search experience. I think it’s awesome. When a user is searching, that’s the mindset they are in and giving them all the space they need to accomplish that goal is great. Here’s me searching (video):

Note that I misspell the word “margin” and the results are fine. You get a full page experience on mobile as well.

Best I can tell, CSS-Tricks gets a couple hundred thousand on-site searches per month, so having a great experience there is very important to me. I don’t even wanna mess around with bad on-site search experiences, or products that are too expensive. I’d rather send people to a site-scoped Google search than a bad on-site search. Fortunately, Instant Search is about as good of an on-site search experience as I can imagine, especially for the zero-work it takes to implement.

Design Control

You have some control over the look of things from the Customizer.

Instant Search is designed to work on any site, so you probably don’t need to do much. I was really surprised how well it worked out-of-the-box for CSS-Tricks. As a CSS control freak, I did ship a handful of design tweaks to it, but that’s just because I love doing that kind of thing.

Tweaks No Longer Needed

With the previous version of Jetpack Search, I had custom code in place to tweak Elasticsearch. I did things like factoring in comment counts as an indicator of popularity, so that I could be sure our best content was high in the results. Remember as powerful as this search is, it doesn’t have a model of the entire internet to calculate relevancy from like Google does. Good news though:

To further improve our search algorithm, we started experimenting with adding the percentage of pageviews from the past 30 days into the index. We ended up finding that pageviews are a much better ranking signal because it somewhat combines both popularity and recency. So now most of our result ranking is strongly influenced by the number of pageviews a post or page gets. Conveniently, if you get a lot of Google Search traffic, our search results should be heavily influenced by Google’s ranking algorithm.

Emphasis mine. With Jetpack Instant Search, I was able to rip all that custom code out (removing code always feels great) because the new algorithms are doing a great job with ranking results.


Now Jetpack Search is ala-carte rather than baked into specific plans. Don’t need it? You don’t pay for it. Only need this feature? You can buy it regardless of what plan you are on.

I’m told the pricing is about scope. Jetpack plans are about features, not scale of site, but that doesn’t make much sense for search where the scale of the site matters a ton. So it’s a sliding scale based on the “records” you have, which are basically posts and pages.

Sliding scale of pricing

I would think a lot of sites fall into the $25/month (15% off for annual) category. You probably mostly start caring about on-site search above 1,000 records and 10,000 records is a ton. I pay for the tier one up from that (~$612 a year) only because our (now archived) bbPress forums pushes the number over 10,000. That’s a perfectly fair price for this for a site like mine.

Wish List

My #1 thing is that I wish it was easy to remove certain things from search results. We have tons and tons of records from our bbPress forums that I made the (hard) call to close this year. Removing those records would pull me down into a smaller pricing tier, but more importantly, I’d rather just not show those results in search at all.

It’s not just CSS-Tricks being in an unusual situation. I’ve also turned on Jetpack Instant Search on the CodePen Documentation.

In that circumstance, I’d consider turning removing blog posts (believe it or not) from the search results, so instead just the Pages would show up which are our core documentation there. Or perhaps even better, blog posts are just turned off as a filter by default, but users could flip them on to see them in the results.

All in all, this is a huge upgrade to Jetpack and yet another reason I consider it the most important plugin I run on my WordPress sites. If you’re curious about other Jetpack features we use, we made a special page for that.

The post Jetpack Instant Search! appeared first on CSS-Tricks.

CSS Scrollbar With Progress Meter

Css Tricks - Wed, 04/15/2020 - 11:49am

Scrollbars are natural progress meters. How far the scrollbar is down or across is how much progress has been made scrolling through that element (often the entire page). But, they are more like progress indicators than meters, if you think of a meter as something that “fills up” as you go.

We can use some CSS trickery to make the scrollbar fill up as we go.

This will only work with -webkit- vendor-prefixed scrollbar-styling properties. In other words, these are non-standard. The standardized scrollbar styling properties are scrollbar-width and scrollbar-color, which can’t pull this kind of thing off, but are probably a safer bet in the long run. Still, the vendor-prefixed versions probably aren’t going anywhere, so if you consider this a weird form of progressive enhancement, that’s probably fine.

What’s the trick?

Essentially, it’s hanging a huge box-shadow off the top of the scrollbar thumb — or off the side if it’s a horizontally scrolling element.

:root { --shadow: #43a047; --scrollbarBG: #eee; --thumbBG: #66bb6a; } ::-webkit-scrollbar { width: 16px; } ::-webkit-scrollbar-track { background: var(--scrollbarBG); } ::-webkit-scrollbar-thumb { background-color: var(--thumbBG); box-shadow: 0 -100vh 0 100vh var(--shadow), 0 0 15px 5px black; } Demo CodePen Embed Fallback

I first saw this in a Pen by Myk.

That example didn’t differentiate the thumb part of the scrollbar at all, which makes it more meter-like, but also harder to use. My demo has a slightly different color thumb.

Can I really use this?

No! Aside from it being super weird and non-standard. Safari flips it’s lid and I have no idea how to fix it.

I do happen to have a favorite CSS trick that is highly related to this though.

I want to learn more about styling scrollbars

Cool, here you go.

The post CSS Scrollbar With Progress Meter appeared first on CSS-Tricks.

Using CSS to Set Text Inside a Circle

Css Tricks - Tue, 04/14/2020 - 4:57am

You want to set some text inside the shape of a circle with HTML and CSS? That’s crazy talk, right?

Not really! Thanks to shape-outside and some pure CSS trickery it is possible to do exactly that. 

However, this can be a fiddly layout option. We have to take lots of different things into consideration, like character count, word count, typeface variations, font sizing, font formatting, and responsive requirements to name a few. One size, does not fit all here. But hey, let’s do it anyway.

Here’s the goal: we want to display a <blockquote> and an author citation inside a circle shape. We also want to make the layout as flexible as we can. This layout won’t require any additional files and keeps the HTML markup squeaky clean.

This is what we’re striving for:

CodePen Embed Fallback

The shape-outside feature is not supported in Internet Explorer or Microsoft Edge 18 and below at the time of this writing.

First up, the HTML

We’re going to end up needing a wrapper element to pull this off, so let’s use the semantic <blockquote> as the inner element. The outside wrapper can be a div:

<div class="quote-wrapper">   <blockquote class="text" cite="http://www.inspireux.com/category/quotes/jesse-james-garrett/">     <p>Experience design is the design of anything, independent of medium, or across media, with human experience as an explicit outcome, and human engagement as an explicit goal.</p>     <footer>– Jesse James Garrett</footer>   </blockquote> </div>

If you’re interested in a deep-dive on the HTML of quotes, you’re in luck. We’re going to set the quote itself in a <p> and the name of the author inside a <footer>. We’ve got class names for the CSS styling hooks we’ll need.

Next, some baseline CSS

Let’s start with the div wrapper. First, we’ll set the minimum (responsive) square size at 300px so it fits on smaller screens. then, we’ll add relative positioning (because we will need it later). 

.quote-wrapper {   height: 300px;   position: relative;   width: 300px; }

Now we’ll make the blockquote fill the whole wrapper and fake a circle shape with a radial gradient background. (That’s right, we are not using border-radius in this example).

.text {   background: radial-gradient(     ellipse at center,     rgba(0, 128, 172, 1) 0%,     rgba(0, 128, 172, 1) 70%,     rgba(0, 128, 172, 0) 70.3%   );   height: 100%;   width: 100%; }

One thing to note is that 70% displays a much rougher edge. I manually added very small percentage increments and found that 70.3% looks the smoothest.

Notice the edge on the right is much smoother than the edge on the left.

Now we have our base circle in place. Add these additional style rules to .text.

.text {   color: white;   position: relative;   margin: 0; }

Here’s what we have so far:

Giving text the CSS treatment

Let’s style the paragraph first:

.text p {   font-size: 21px;   font-style: italic;   height: 100%;   line-height: 1.25;   padding: 0;   text-align: center;   text-shadow: 0.5px 0.5px 1px rgba(0, 0, 0, 0.3); }

Let’s use the blockquote’s ::before pseudo-element to create our shaping. This is where the shape-outside property comes into play. We plot out the polygon() coordinates and float it to the left so the text wraps inside the shape.

.text::before {   content: "";   float: left;   height: 100%;   width: 50%;   shape-outside: polygon(     0 0,   98% 0,     50% 6%,     23.4% 17.3%,     6% 32.6%,     0 50%,     6% 65.6%,     23.4% 82.7%,     50% 94%,     98% 100%,     0 100%   );   shape-margin: 7%; }

Let’s change the radial background color to red. The path editor polygon points and connecting lines are also blue. We are changing this color temporarily for greater contrast with the editor tool.

background: radial-gradient(   ellipse at center,   rgba(210, 20, 20, 1) 0%,   rgba(210, 20, 20, 1) 70%,   rgba(210, 20, 20, 0) 70.3% );

I like Firefox’s developer tools because it has super handy features like a shape-outside path editor.  Click on the polygon shape in the inspector to see the active shape in the browser window. Big thumbs up to the Mozilla dev team for creating a very cool interface!

The Firefox shape editor tool also works for clip-path and <basic-shape> values.

Here’s what we have at this point:

Those points along the shape are from Firefox’s editing tool.

We can do the same sort of thing for the paragraph’s ::before pseudo-element. We use the shape-outside to make the same polygon, in reverse, then float it to the right.

.text p::before {   content: "";   float: right;   height: 100%;   width: 50%;   shape-outside: polygon(     2% 0%,     100% 0%,     100% 100%,     2% 100%,     50% 94%,     76.6% 82.7%,     94% 65.6%,     100% 50%,     94% 32.6%,     76.6% 17.3%,     50% 6%     );   shape-margin: 7%; }

Looking good, but where did the footer go? It overflowed the <blockquote> (where the circular colored background is), so we’re unable to see that white text on a white background.

Styling the footer

Now we can style the <footer> and give it an absolute position to bring it back on top of the circle.

.quote-wrapper blockquote footer {   bottom: 25px;   font-size: 17px;   font-style: italic;   position: absolute;   text-align: center;   text-shadow: 0.5px 0.5px 1px rgba(0, 0, 0, 0.3);   width: 100%; }

Again, feel free to change the background color to suit your needs.

This is where the fiddly part comes in. The text itself needs to be styled in such a way that the number of words and characters work inside the shape. I used these CSS rules to help make it fit nicely:

  • font-size
  • shape-margin (we have two exclusion areas to adjust)
  • line-height
  • letter-spacing
  • font-weight
  • font-style
  • min-width  and min-height (to size of the .quote-wrapper container)
Adding the quote mark for some flourish

Did you see the giant quotation mark in the original demo? That’s what we want to make next.

We’ll take advantage of the ::before  pseudo-element for .quote-wrapper. Yet again, this will take a fair amount of fiddling to make it look right. I found line-height has a huge effect on the mark’s vertical position.

.quote-wrapper::before {   content: "\201C";   color: #ccc;   font-family: sans-serif, serif;   font-size: 270px;   height: 82px;   line-height: 1;   opacity: .9;   position: absolute;   top: -48px;   left: 0;   z-index: 1; } .dumb-quotes::before { content: "\0022"; } .dumb-quotes::after { content: "\0022"; }

There’s actually a difference between curly (“smart”) quote marks and straight (dumb) ones. I’d suggest using curly quote marks for dialogue and straight quote marks for coding.

Handling responsive styles

We should probably make our quote bigger on larger screens. I’m setting a breakpoint at 850px, but you may want to use something different.

@media (min-width: 850px) {   .quote-wrapper {     height: 370px;     width: 370px;   }   .quote-wrapper::before {     font-size: 300px;   }   .text p {     font-size: 26px;   }   .quote-wrapper blockquote footer {     bottom: 32px;   } } There we have it! CodePen Embed Fallback

We set HTML text inside a circular shape using a combination of old and new CSS techniques to make an appealing <blockquote> that commands attention. And we achieved our display goal without any additional dependencies, while still keeping the HTML markup clean and semantic.

I hope this article encourages you to explore new layout possibilities with shape-outside. Stay tuned for shape-inside.

The post Using CSS to Set Text Inside a Circle appeared first on CSS-Tricks.

Syndicate content
©2003 - Present Akamai Design & Development.