DEV Community

Cover image for RunCSS, A Runtime Version of TailwindCSS and Beyond
Nick Mudge
Nick Mudge

Posted on • Edited on

RunCSS, A Runtime Version of TailwindCSS and Beyond

TailwindCSS is a utility-first CSS framework for rapidly building custom designs. Hell yea!

TailwindCSS is what it says it is. I love TailwindCSS.

I don't love the installation of TailwindCSS.

  1. I don't want to be forced to install a node.js package in order to use a CSS framework. That ties my development to node.js. I don't like that. What if I want to use Deno or something else?
  2. I don't want to process my css with postcss, or the tailwind command line program or a build tool such as webpack etc. They are probably good software but I'd rather not use them if I don't have to.
  3. I don't want to run a purge program to remove unused CSS styles. I don't want to follow practices to enable unused CSS to be removed.

I just want to use TailwindCSS. And I want it to work well. No house keeping or house building please. No dependencies please.

What About The CDN Build?

Instead of installing I could use the CDN build.

<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
Enter fullscreen mode Exit fullscreen mode

TailwindCSS documentation says this CDN file size is 27kb compressed and 348kb raw. But this is not true. It is just outdated documentation. If you measure this file today like I did you find that it is 135kb compressed and 1,328kb raw.

But it isn't important. The TailwindCSS documentation dissuades people from using the CDN file in production with this:

To pull in Tailwind for quick demos or just giving the framework a spin, grab the latest default configuration build via CDN.

It is also not possible to customize the CDN file and some pseudo-class variants are missing.

So this comes down to:

  1. I don't want to install TailwindCSS.
  2. I don't want to process my CSS with build tools.
  3. I don't want to purge my CSS.
  4. I don't want to use a huge bloated CSS file that is limited and can't be customized.
  5. I want to use TailwindCSS!

The solution is RunCSS.

Enter RunCSS

RunCSS is a runtime version of TailwindCSS. It has no build. RunCSS provides all the same CSS utility class names that we know and love from TailwindCSS.

RunCSS is batteries included. It has feature parity with TailwindCSS and beyond. RunCSS defaults are the same as TailwindCSS defaults plus TailwindCSS's additional variants, plus more. By default all variants such as hover, active, visited, group-hover etc. and responsive variants such as sm, lg etc work with all class names.

RunCSS is possible because it is a Javascript file that generates CSS at runtime.

The primary difference between TailwindCSS and RunCSS is that TailwindCSS generates CSS at build time and RunCSS generates CSS at runtime.

RunCSS has no build. Just use it. Off to the races!

The tradeoff to using RunCSS is a small amount of Javascript execution to generate CSS at runtime. The necessary CSS for each class name is generated one time as it is encountered. CSS is only generated for class names that are actually used.

How to use RunCSS

Step 1. Add a CSS reset or base CSS file, such as TailwindCSS's preflight, to your web application:

<link href="https://unpkg.com/runcss@^0/dist/preflight.css" 
rel="stylesheet">
Enter fullscreen mode Exit fullscreen mode

Step 2. Import the RunCSS Javascript file into your application:

import processClasses from 'https://unpkg.com/runcss@^0/dist/runcss.modern.js'
Enter fullscreen mode Exit fullscreen mode

Step 3. Call the processClasses function on CSS class names. It is possible to integrate RunCSS into existing Javascript libraries so that processClass is called automatically when CSS class names are used. RunCSS ignores class names it has already generated CSS for so processClasses can be called repeatedly on the same class names.

Example

Here is an example that integrates RunCSS with Webscript and creates the same card example given on TailwindCSS's homepage:

// Importing Webscript
import builders from 'https://unpkg.com/webscript@^0/dist/webscript.modern.js'
import createDOMElement from 'https://unpkg.com/webscript@^0/dist/createDOMElement.modern.js'
// Importing RunCSS
import processClasses from 'https://unpkg.com/runcss@^0/dist/runcss.modern.js'

// Integrating RunCSS with Webscript
function createElement (type, props, ...children) {
  if (props.class) {
    processClasses(props.class)
  }
  return createDOMElement(type, props, ...children)
}

// Create the builders used to build DOM elements
const { div, img, h2 } = builders(createElement)

// Card display
const card =
  div.class`md:flex bg-white rounded-lg p-6`(
    img.class`h-16 w-16 md:h-24 md:w-24 rounded-full mx-auto md:mx-0 md:mr-6`.src`./avatar.jpg`,
    div.class`text-center md:text-left`(
      h2.class`text-lg``Erin Lindford`,
      div.class`text-purple-500``Customer Support`,
      div.class`text-gray-600``erinlindford@example.com`,
      div.class`text-gray-600``(555) 765-4321`))
Enter fullscreen mode Exit fullscreen mode

Here is the result of the above code:
Result of above code

Using RunCSS Without a Javascript Library

Here is a simple example of how to use RunCSS without integration with an existing Javascript library or framework:

<html>
<head><head>
<!-- Prevent flash of unstyled elements with display:none -->
<body style="display: none;">
  <!-- HTML that uses RunCSS here. -->
  <div class="md:flex bg-white rounded-lg p-6">
    <img class="h-16 w-16 md:h-24 md:w-24 rounded-full mx-auto md:mx-0 md:mr-6" src="avatar.jpg">
    <div class="text-center md:text-left">
      <h2 class="text-lg">Erin Lindford</h2>
      <div class="text-purple-500">Customer Support</div>
      <div class="text-gray-600">erinlindford@example.com</div>
      <div class="text-gray-600">(555) 765-4321</div>
    </div>
  </div>    
  <!-- This code generates all the CSS needed for the webpage. -->
  <script type="module">
    import processClasses from 'https://unpkg.com/runcss@^0/dist/runcss.modern.js'
    // Get all elements that have a class attribute.
    for(const element of document.querySelectorAll('*[class]')) {    
      processClasses(element.className)
    }
    // Display elements
    document.body.style.display = "block"
  </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

RunCSS File Size

runcss.modern.js is 8kb compressed and 20kb raw. It has no dependencies.

Optional Node.js Package

RunCSS can optionally be installed like this:

npm install runcss
Enter fullscreen mode Exit fullscreen mode

Going Beyond TailwindCSS

Because RunCSS doesn't have build-time constraints it can easily go beyond TailwindCSS and it does. RunCSS provides all the same utility CSS class names that TailwindCSS does plus many more.

For example, by default, TailwindCSS's margin classes have holes in them. There is m-6, but no m-7. There are no margin classes between m-24 and m-32. The margin classes stop at m-64. With TailwindCSS it is possible to plug these holes by manually adding configuration to the TailwindCSS build configuration file. RunCSS doesn't require configuration and has no such holes and the class names don't end. RunCSS includes m-65 and m-66 and so on forever or until the browser can't take it anymore.

But there is more. RunCSS accepts any valid CSS length unit in many class names. For example, you could use m-5% or m-1.25rem or m-25px or whatever valid CSS length unit you want to use.

One of the benefits of using utility classes is "designing with constraints". It is easier to build consistent visual designs if you pick your styles from a limited set. With RunCSS this can be done by convention and enforced, if desired, by a linter. In addition with RunCSS you can go outside your design system in special cases where you need maximum control.

Many of the following sections show RunCSS's extended capabilities.

Configuration

RunCSS provides the configure function that can be used to configure parts of RunCSS. The following sections in this article that can use configure show how to use it.

Colors

RunCSS provides the same default color palette as TailwindCSS.

These colors can be used in all the same class names as can be used in TailwindCSS. They can be used in text, borders, placeholders, divides, and backgrounds.

Color Example:

// Using Webscript with RunCSS
div.class`bg-blue-500 border-3 border-yellow-700`(
  p.class`text-white``Example Colors`
)
Enter fullscreen mode Exit fullscreen mode

Did you know that CSS specifications and browsers support 150 color keywords? RunCSS supports them all too. From black to rebeccapurple.

RunCSS supports all valid CSS color formats. For example hex, rgb/rgba and hsl/hsla formats

Here is an example that uses various color formats:

div.class`bg-rebeccapurple border-10 border-rgba(200,10,10,0.1)`(
  p.class`text-hsl(120,100%,60%) xl:text-#ecc94b``Example Colors`
)
Enter fullscreen mode Exit fullscreen mode

Note: Make sure there are no spaces in your class names because class names are separated by spaces.

It is possible to make your own color palette by configuring colors with the configure function. You can create your own color keywords.

Here is an example that sets the 'blue' keyword to the color red and sets some banana colors:

// import the configure function
import processClasses, { configure } from 'https://unpkg.com/runcss@^0/dist/runcss.modern.js'

// create our own color palette
configure({
  colors: {
    blue: '#ff0000',
    banana: {
      100: '#FFFFF0',
      200: '#FEFCBF',
      300: '#FAF089'
    }
  }
})
Enter fullscreen mode Exit fullscreen mode

Note that the CSS for these color classes is only generated if they are used.

Note that only hex values can be used in the configure function for colors.

Responsive Design

Responsive design with RunCSS works the same way as it does with TailwindCSS. Checkout Tailwind's documentation about it.

By default RunCSS provides the same responsive breakpoints as TailwindCSS:

{ sm: '640px', md: '768px', lg: '1024px', xl: '1280px' }
Enter fullscreen mode Exit fullscreen mode

Just like TailwindCSS all RunCSS classes can use the breakpoint prefixes without any configuration. However, in addition, any CSS class not generated and coming from RunCSS can use them too!

For example, if you create your own CSS file with some custom CSS you don't have to create media queries for different breakpoints. Just use the responsive prefixes from RunCSS.

Example

Here is a custom CSS file. Notice there are no media queries for responsive versions of the class:

.myclass {
  margin: 0 10px;
  background-color: red;  
  border-radius: 0.5rem; 
}
Enter fullscreen mode Exit fullscreen mode

Go ahead and make it responsive by using RunCSS's responsive prefixes in your DOM building code:

div.class`lg:myclass`(
  p`Example text`
)
Enter fullscreen mode Exit fullscreen mode

RunCSS only generates CSS for responsive breakpoint classes that are used.

Configure Your Own Responsive Breakpoints

You can set your own responsive breakpoints and prefixes by calling RunCSS's configure function. Here is an example:

configure({
  screens: { 
    watch: '300px',
    phone: '340px',      
    tablet: '640px'
   }
})
Enter fullscreen mode Exit fullscreen mode

Note: Make sure you configure screens before you start processing CSS class names with processClasses.

Pseudo-Class Variants

Pseudo-class variants like hover, focus etc. work with RunCSS class names the same way they do with TailwindCSS class names.

TailwindCSS provides a number of pseduo-class variants that are not enabled by default due to file-size constraints.

RunCSS, not having build file-size constraints, has enabled, by default, all of TailwindCSS's pseudo-class variants.

RunCSS only generates the needed CSS for the class names and variants that are actually used.

By default, RunCSS also provides and has enabled all psuedo-class and psuedo-element variants that are supported by web browsers.

Just like RunCSS responsive prefixes can be used by CSS class names from third-party CSS style sheets, RunCSS's psuedo-class and psuedo-element prefixes can be used by CSS class names from third-party CSS style sheets.

Example

Here is a custom CSS file. Notice there are no psuedo-class versions of the class name:

.myclass {
  margin: 0 10px;
  background-color: red;  
  border-radius: 0.5rem; 
}
Enter fullscreen mode Exit fullscreen mode

Go ahead and apply a RunCSS pseudo-class prefix to it:

div.class`hover:myclass`(
  p`Example text`
)
Enter fullscreen mode Exit fullscreen mode

No configuration for pseudo-classes and pseudo-elements is needed because they are all available.

Extracting Components

RunCSS provides the component function to create CSS components. This is a way to create your own CSS utilities or components using RunCSS class names and/or CSS properties.

The component(name, classNames, properties) function takes three strings as arguments. The third argument is optional.

CSS will be generated using the last two arguments.

Component Example

import processClasses, { component } from 'https://unpkg.com/runcss@^0/dist/runcss.modern.js'

component(
  'btn', // new class name
  'p-2 bg-blue text-white hover:text-green-500 text-base lg:text-lg', // extracting CSS from class names
  'box-shadow: 0px 8px 15px rgba(0, 0, 0, 0.5); outline: none;' // using CSS properties
)

// Use the CSS component
const button = button.class`btn text-yellow``Click Me`
Enter fullscreen mode Exit fullscreen mode

RunCSS utility class names will override CSS components. This enables you to customize or specialize CSS components when they are used.

You can think of CSS components as default styles that can be overridden with utility classes.

In the example above the text-yellow class overrides the text-white class that is defined in the CSS component.

Increasing Specificity with Important

You can increase specificity of your RunCSS utilities by calling configure with {important: true}. That will add !important to RunCSS styles.

If you want more specificity but less than !important then give the important option a selector. Like this: {important: '#app'}. And make sure that your RunCSS classes are added under an element with the 'app' id or whatever you specified.

Prefix

It is possible to add a prefix to all RunCSS utilities by calling configure with a prefix option.

Here is an example:

configure({ prefix: 'run-' })

div.class`run-text-blue hover:run-text-yellow`(
  p`My test`
)
Enter fullscreen mode Exit fullscreen mode

Separator

Instead of using : to separate variants such as hover, sm, focus and the rest you can use a different separator. Call configure with the separator option. Here is an example:

configure({separator: '$'})

div.class`run-text-blue hover$run-text-yellow`(
  p`My test`
)
Enter fullscreen mode Exit fullscreen mode

No Build Movement

RunCSS is another tool that's part of the No Build Movement.

The No Build Movement is a change in web development that favors building web applications without build tools except for minification of resources.

Project Home

The RunCSS project can be found here:

GitHub logo mudgen / runcss

A utility-first CSS runtime for rapid UI development.

RunCSS

RunCSS is the runtime equivalent of TailwindCSS, featuring the same CSS utility class names, but with no build step required. It achieves this by generating CSS on the fly with JavaScript.

RunCSS comes with batteries included. By default all additional variants such as hover, active, visited, group-hover, sm, lg etc work with all class names. All packaged in a single 25kb (8kb after compression) JS file!

Usage

Add to <head>:

  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/runcss/dist/runcss.min.css">
  <script src="https://cdn.jsdelivr.net/npm/runcss/dist/runcss.min.js" defer watch></script>
Enter fullscreen mode Exit fullscreen mode

Done! RunCSS will parse the documents and generate the corresponding classes, with no further configuration required. RunCSS will also watch for new element insertions and parse them. Remove the watch attribute to disable this feature.

Check the examples/ directory for some examples.

Avoid element popup

You may add the runcss-cloak




Follow me on twitter.

Top comments (8)

Collapse
 
mullojo profile image
Bob

This seems pretty cool Nick 👍 I followed your comment on the Meteor + TailwindCSS post. I'm developing a lot with Meteor, Vue, and Bootstrap personally, but I'm also actively working to contribute to both the Meteor and Vue open source projects.

Meteor has a mostly "magical" build tool that makes most developers not even ever realize it's happening, however, we're also seeing Svelte + Meteor apps being built and Svelte seems to take the Runtime approach as well.

I encourage you to come join the Meteor JS community and share some of your work. The Meteor devs love to see & support the growth of new JS projects and approaches. There have even been some pretty active discussions about Deno over there, even though Meteor is build pretty heavily on Node JS.

It would be really cool if you built a quick demo project with an integration of Meteor and RunCSS. You could demo it with React, Vue, Svelte, or any other front-end (aka view layer) you might like.

You can join our very active & friendly Meteor Forums here: forums.meteor.com/

There are two recent post about using TailwindCSS w/ Meteor, so maybe you could add your thoughts 💭 / comments.

And also we have a Meteor Slack that is pretty active.

Collapse
 
mudgen profile image
Nick Mudge

Hey Bob, that's great! I will come over to the forum. Thank you so much! I am glad to hear that the Meteor community is welcoming like that.

Collapse
 
mullojo profile image
Bob

Hey Nick! I gave you an intro over on a forum post where we're inviting JS devs to try out Meteor. forums.meteor.com/t/inviting-100-j...

Meteor has usually been seen as a "bloated" thing vs. a light weight, configure anything you like kind of solution, but we've really been reshaping Meteor over the years to be more configurable and to keep devs from having to reinvent the wheel, so it would be fun to see how you might incorporate RunCSS into a Meteor project.

Collapse
 
artydev profile image
artydev

Hy Nick,

Thank you for your work.
Shadow does not seems to work...
Have you an example with a simple card ?

Regards

Collapse
 
mudgen profile image
Nick Mudge

Thanks for telling me! I fixed it. Shadow works now.

Collapse
 
artydev profile image
artydev

Great, thank you :-)

Collapse
 
mudgen profile image
Nick Mudge

Hey, there. Preact should have a function that gets called on every element. For example React has the function React.createElement that gets called by JSX. Replace that function with your own function that calls that function and also calls processClasses on class attributes.

Collapse
 
dinbtechit profile image
Dinesh

This is super interesting. Thank you for posting this. I have to definitely try this out.