Dynamic styling in neetoSite using styled-components
neetoSite is a website-building tool currently in development. As we continue to develop neetoSite, we have encountered challenges in implementing dynamic styling for the blocks. Let's see the various strategies we've attempted to address these challenges and discuss how we solved the issue.
1. Navigating the dynamic styling challenge
Dynamic styling enables users to apply personalized styles to their website blocks, such as modifying the font size, background color, or other visual aspects.
This customization allows users to craft different and visually appealing websites tailored to their preferences. One possible solution to achieve dynamic styling is using inline styles; however, we didn't adopt this approach because it could negatively impact the website's SEO. With this in mind, we explored alternative solutions that wouldn't compromise SEO while still providing the desired level of customization.
1. Tailwind CSS and string interpolation
Our initial approach for tackling dynamic styling involved using TailwindCSS and string interpolation. String interpolation allowed us to dynamically insert values into a string, enabling us to create custom class names for our components.
const { fontWeight, fontSize } = design.body;
const className = `font-${fontWeight} text-${fontSize}`;
return <div className={className}>block content</div>;
This approach worked seamlessly for most styles, but it offered little customization concerning dynamically selecting custom colors. We needed to explore other solutions.
2. The Just-in-Time (JIT) compilation consideration
Just-in-Time(JIT) compilation is a feature introduced in Tailwind v2.1+ that offers significant performance improvements compared to the normal compilation process. Unlike the normal compilation process, which generates all possible class combinations regardless of their usage, JIT compilation scans the codebase for class names. It generates styles only for the classes used. This results in a smaller and more optimized CSS output. Additionally, JIT compilation provides enhanced customization options, such as using arbitrary hex color values directly in class names.
// Example of using arbitrary hex color with JIT compilation
<div className="bg-[#1c64f2] text-[#a78bfa]">block content</div>
We considered using the JIT compilation feature, hoping it would work seamlessly with our existing setup and keep everything within the Tailwind ecosystem. With the ability to use arbitrary hex color values directly in class names, the JIT compilation seemed like a promising solution for our dynamic styling requirements.
// Example of using arbitrary hex color with JIT compilation and string // interpolation
const { backgroundColor, textColor } = design.body;
const className = `bg-[${backgroundColor}] text-[${textColor}]`;
return <div className={className}>block content</div>;
In this example, we combined JIT compilation with string interpolation to achieve dynamic styling. Unfortunately, the combination of these two techniques did not work as expected. The JIT compiler had trouble recognizing the dynamically generated class names, failing to generate the correct styles. Consequently, the JIT compilation did not provide the expected benefits, and we had to explore alternative solutions.
3. Introduction of CSS variables
As a workaround, we introduced CSS variables to manage the color properties provided by users. We only added CSS variables for colors, as users shouldn't be constrained to the Tailwind color palette and should be free to use any color they desire. Additionally, adding all properties to the CSS variables would make the file cumbersome and challenging to maintain. But this approach led to an undesired effect when two identical blocks were added: changing the color of one block would also change the color of the other block due to the shared CSS variables.
4. Purging of CSS classes and discovering new problems
When we enabled the purging of Tailwind classes to optimize the bundle size, we encountered a significant issue: most classes were incorrectly purged in the staging environment. Upon investigation, we discovered that string-interpolated classes, such as bg-${color}-600 hover:bg-${color}-500, cannot be identified at compile time. This issue stems from string interpolation being considered an anti-pattern in the Tailwind ecosystem. Consequently, using dynamically constructed classes like this can lead to problems. To avoid these issues and maintain optimal bundle size, we should always create static classes that can be determined during the build process.
These challenges led us to explore alternative solutions, such as styled-components, to address the issues associated with dynamic styling, shared CSS variables, and purging.
5. Embracing styled-components
After thorough analysis, we concluded that styled-components, a CSS-in-JS library, offered the ideal solution for our needs. By adopting styled-components, we could directly access the props passed to the components and apply the values to the CSS properties without resorting to string interpolation. This approach resolved the purging issue, as we no longer relied on string interpolation for classes. Furthermore, it enabled unique styles for different blocks, as we were no longer dependent on CSS variables for colors.
Upon refactoring, we discovered that styled-components significantly reduced the boilerplate code needed for each block, shrinking each block to roughly 1/4 of its original size. This optimization allowed us to eliminate the ever-growing list of CSS variables and significantly reduce the package's bundle size while achieving the desired dynamic styling.
To better understand the process, let's first look at the design.body
object, which contains the various style properties for a block:
{
"borderTopWidth": 1,
"borderTopStyle": "solid",
"borderBottomWidth": 1,
"borderBottomStyle": "solid",
"backgroundSize": "cover",
"backgroundPosition": "center",
"backgroundRepeat": "no-repeat",
...otherSimilarProps
}
By properly naming the keys in the design.body object and utilizing styled-components, the desired styles were applied dynamically, resulting in a more seamless and efficient implementation. Here's a comparison between our previous approach and the new solution using styled-components:
// Before using styled-components
<div
id={id}
className={`border border-l-0 border-r-0 border-t-${borderTopWidth} border-t-${borderTopStyle} border-b-${borderBottomWidth} border-b-${borderBottomStyle} bg-${backgroundSize} bg-${backgroundPosition} bg-${backgroundRepeat}`}
>
block content
</div>
// After abstracting every value to styled-components
<StyledWrapper id={id} design={design.body}>
block content
</StyledWrapper>
As shown in the code snippets above, the transition to styled-components significantly improved the maintainability and readability of our code. This new approach allowed us to access the design.body
object directly and apply the styles according to the values in the object, making the dynamic styling process more efficient and user-friendly.
Now, let's take a look at the StyledWrapper
implementation:
import styled from "styled-components";
const StyledWrapper = styled.div(({ design = {} }) => design);
export default StyledWrapper;
This simple implementation of the StyledWrapper component enables it to receive the design prop and apply the styles dynamically using the power of styled-components.
Wrapping up
In conclusion, our work on neetoSite demonstrates the importance of being open to different approaches and technologies when addressing development challenges. Our experience with dynamic styling and web application development has deepened our understanding of how tailored technologies can improve specific use cases. We encourage you to try out neetoSite and experience its user-friendly features. Your feedback is greatly appreciated, as it helps us refine and enhance the platform, ultimately leading to a better user experience.
neetoSite has not been launched for everyone yet. If you want to give it a try, then please send an email to invite@neeto.com. neeto has many other products for you to check out.
Subscribe to this neeto blog to see what we are up to. We'll spill the beans on our out-of-this-world marketing maneuvers (we write blogs like this one). Prepare to have your jaw hit the floor as we reveal revenue numbers that make Elon Musk jealous(we might need a few more zeroes to reach "buy-an-island" status).
And when it comes to reeling in new customers, we've mastered the art of hypnotizing business folks with our mesmerizing PowerPoint presentations that make them chant "neeto!" while doing the Macarena. So buckle up, my daring friend, and hop on board the neeto rollercoaster of insanity!
Warning: Side effects may include an irrepressible desire to shout "neeto!" from the highest rooftops.Welcome to the land of outrageousness, where the mundane is banished and extraordinary reigns supreme! Let the absurdity begin as we embark on a mission to leave our mark on the world, one glorious dent at a time.