DEV Community

Greg Fletcher
Greg Fletcher

Posted on

How to Simplify Stateful CSS Styles

Today, I'm going to present another way to organize stateful-styles. By stateful-styles, I mean the CSS styling that changes in response to your Application's State.

Reasons why I'm writing this

Recently my front end development code has become easier to read. Why? Because I've been using state machines and data attributes to help simplify the different states of my application.

Before I started using state machines and data attributes, I was hacking around with many kinds of unrelated states and quite often ended up with code that was hard to read. This made development difficult and discouraged my exploration of cool animations and styling. But now, because I use state machines, I always think about the different kinds of states that my application can exist in. As a result, I've seen a huge reduction in spaghetti (messy) code and complicated boolean logic in my applications.

I learned this style of programming from David Khourshid and Stephen Shaw. Two amazing developers who have an incredible YouTube channel called keyframers. Check them out!

I genuinely believe that state machines are a game-changer for UI development and I hope that I can encourage you to try them out yourself. I'll leave some important resources at the bottom of this article.

Now that's out of the way, let's start.

What Tools We'll Use

  • Data attributes
  • A state machine
  • React
  • SCSS (Sass)

Let's start by talking about data attributes.

Data Attributes

  • Data attributes are just attributes that you can put on an HTML element.
  • You can name a data attribute however you like, so long as it starts with the keyword data.

So why use data attributes? Data attributes make it easier to think about your styles in a stateful way. If the state changes, then the appropriate styling changes as well. If you combine this with a state machine, your code can become simpler because you're not relying on multiple, potentially conflicting, states.

That might seem a bit vague but it will become clearer as we go along.

Let's take a look at a data attribute and then how we would use it in our CSS.

<div data-view='on'>

<p class="Text">Some Text</p>

</div>

👆 The div above has a data attribute called data-view='on'. We can use this attribute in our CSS below 👇

.Text {
color: black;
}

[data-view='on'] { /* the styling below applies only when the data attribute exists */
.Text {
color: red;
}

Okay, let's unpack this quickly.

The [data-view='on'] is a CSS selector that only applies its styling if the data attribute (data-view) exists. If it does exist, then it takes precedence over the original color styling of black. If it does not exist, then the default black is applied instead.

What's important to note here is that we're moving into a direction where we can start to think about our Application's styling in a stateful way. This emphasis on statefulness will make it much easier to organize our CSS and Javascript.

Before we go any further, let's have a look at the example project that I made with data-attributes and a state machine. I replicated a Dribble project by Jaydeep Patel.

(click on the button to interact).

Okay, so this app has two states, on and off. That's it!

Let's take a look at the code.

First the JSX.

<div  data-view={state} className="App">
<div className="OnPage" />
<div className="OffPage" />
//...extra markup
</div>

Now the CSS.

:root {
  --transition:  transform .4s ease-in-out;  
}

/* default styles */
.OffPage {
  background: #050033;
  transform: translateX(100%);
  transition: var(--transition);
}
.OnPage {
  background: #1da34d;
  transform: translateX(-100%);
  transition: var(--transition);
}

/* data attribute styles */
[data-view="off"]{
  .OffPage{
    transform: none;
  }
}
[data-view="on"]{
  .OnPage{
    transform: none;
  }
}
  1. We have default styling that transforms both OnPage and OffPage out of view using translateX.
  2. Depending on the current state we apply a transform of none to OffPage and the OnPage classes. Because we have a default transition of .4s the element appears to slide in. It's important to note that these styles only appear if the correct data attribute exists.

This is really useful because now we can divide our CSS into two different states, making our styling much easier to organize.

Now, we need to switch between the on and off states, so let's take a look at the state machine.

State machines

I first heard about state machines from one of David Khourshid's tech talks. His approach was so wonderfully simple that I now try to apply state machines in all of my front end code! You can check out David's talk here.

I know that 'state machines' might sound scary to those of you who haven't heard of them before (they sounded scary to me at first!) but once you understand them you'll discover a wonderfully simple way of organizing state in your Applications.

Key State Machine Concepts

  1. There are finite states in your application. No impossible or unthought-of states. This helps to reduce bugs.
  2. Finite events that trigger state changes.
  3. State machines make you explicitly think about your application state. It's either on or off.
  4. There is an initial state.

David Khourshid has written more about this for his state machine library, XState. Check out XState's documentation.

Let's take a look at some state machine code.


const machine = {
  initialState: 'on',
  states: {
  on: 'off',
  off: 'on'
};
const [state, setState] = useState(machine.initialState);
const cycle = (state) => setState(machine.states[state]);
return <div data-view={state} className="App">
//...extra markup
<div onClick={() => cycle(state)} className="CircleContainer">
//...extra markup
</div>

That's all the logic we need for the App!

It might be a bit confusing still so I'll explain.

  1. First, we create the machine which is just an object with 2 outer keys, initialState and states. States has two inner keys on and off. Both of which have the value of the next key.
  2. Next, we create state and setState with useState whilst applying the initialState of on.
  3. Then, we use the cycle function to cycle through the states on and off. This always returns the next state. For example, if the current state is on then it will return off. Then the cycle repeats.
  4. Finally, we set data-view to the current state. Whenever that changes, our CSS will change accordingly!

Just to be clear, every single style change that you see occurs when the data-view attribute changes from 'on' to 'off'.

This makes the CSS easier to organize. Let's take a look at the text sliding up and down.

.OnText, .OffText {
  font-size: 5rem;
  color: white;
  overflow: hidden;
  span {
    transition: transform .4s ease-in-out;
  }
}

.OnText {

  span {
    display: block;
    transform: translateY(100%);
  }
}
.OffText {

  span {
    display: block;
    transform: translateY(-100%);
  }
}

[data-view="on"]{
  .OnText {
    span {
      transform: none;
    }
  }
}

[data-view="off"]{
  .OffText {
    span {
      transform: none;
    }
  }
}

All of the style changes you see come from [data-view="on"] and [data-view="off"].

What's also nice is that the state machine doesn't require tricky booleans in the JSX. The only thing we change is the value of data-view. It's either 'on' or 'off'. I think that this greatly simplifies things.

Let's see that again.

<div data-view={state} className="App">

Some of you might be thinking that you could have achieved this same effect by using classes and swapping them out. That's true. But I find matching a state machine with data attributes much simpler. Maybe it's personal taste, maybe not. Either way, I encourage you to try it out in your next side project!

Let's take another look at the App. I recommend forking it and experimenting by changing the styles underneath the data attributes. See if you can come up with something different! Feel free to post a link to your version in the comments!

Recap

State machines and data attributes might seem tricky at first but in reality, they can simplify development by dividing our styling into separate states.

To illustrate this we saw an example App where every style change was related to the 'on' and 'off' states. We utilized data attributes in the CSS to apply stateful styles and set up the logic in the JSX with a simple state machine object and useState.

Finally, I'd encourage you to do some research into state machines and data attributes. I've listed some resources below!

Thanks for reading!

Resources

State machines information:
XState

Data attributes:
KeyFramers

Discussion (0)