I'm wondering if a vanilla-approach (without helpers libraries) is still a valid choice...
I'm not sure what you mean here, can you explain. Would 'vanilla approach' mean using a CSS Stylesheet with CSS Selectors for some or most of these styles? I think you should try to limit the CSS-in-JS to only the properties or values that depend on JavaScript.
I obliviously can add a className to each of my components but I'm trying to adopt CSS-in-JS best practice in order to use all the goodies of let JS managing my styles and to have a scoped CSS for each component.
What goodies are those? I do stuff with style scoping all the time, but never via inline styles. Scoped styles are something I do with JS-in-CSS (as opposed to CSS-in-JS) but usually it's for element queries, container queries, or using the properties of the elements I'm styling to inform the CSS I'm writing. When I write scoped styles I'm less concerned with getting my CSS to apply to elements as I am using JavaScript to help me figure out how to write the values in the CSS I'm applying.
Is there a particular set of tools you're using right now. Scoped styles should be as easy as this:
<div class="widget">
<h2>Widget Title</h2>
</div>
<div style="width: 75%">
<div class="widget">
<h2>Widget Title</h2>
</div>
</div>
<div style="width: 50%">
<div class="widget">
<h2>Widget Title</h2>
</div>
</div>
<script>
window.addEventListener('load', JSinCSS)
window.addEventListener('resize', JSinCSS)
window.addEventListener('input', JSinCSS)
window.addEventListener('click', JSinCSS)
function JSinCSS() {
var tag = document.querySelector('#JSinCSS')
if (!tag) {
tag = document.createElement('style')
tag.id = 'JSinCSS'
document.head.appendChild(tag)
}
tag.innerHTML = `
/* Global CSS goes anywhere */
body {
background: tan;
}
/* Evaluating global JS in CSS */
body:before {
content: '${innerWidth} x ${innerHeight}'
}
/* Running JS from CSS using functions */
${scopedStyle('.widget', `
$this {
border: 1px solid red;
}
`)}
`
}
// A simple style-scoping plugin
function scopedStyle(selector, stylesheet) {
// Find all elements in document matching selector
var tag = document.querySelectorAll(selector)
var style = ''
var count = 0
// For each element matching selector
for (var i=0; i<tag.length; i++) {
// Make a unique identifier
var attr = btoa(selector).replace(/=/g, '')
var element = '[data-' + attr + '="' + count + '"]'
// Mark the element with the unique identifier
tag[i].setAttribute('data-' + attr, count)
// Replace `$this` with that same identifier in CSS
var css = stylesheet.replace(/\$this/g, element)
// Add a duplicate of the scoped CSS rule to the stylesheet we're returning
style += css
count++
}
// Return all scoped CSS to JS-in-CSS stylesheet
return style
}
</script>
That's a working JS-in-CSS function hooked up to 4 global events the reprocesses a CSS stylesheet that can be augmented with anything that JavaScript knows.
The way the CSS reprocessor works is is creates a <style> tag in the document, and every time the events it's watching are triggered, it recalculates the contents of that <style> tag.
In order to 'scope' the application of a style to each element that matches a selector (individually) all that was required was to count each element and assign them a unique identifier (if you're using JS to build your app maybe you can do the 'unique naming' part as you render your HTML in JS…) and then for each matching element I also took the supplied Stylesheet with a special placeholder word, here I've used $this and I will add a copy of the held stylesheet, but replacing $this with the same unique identifier for the element we want the rule to apply to.
$this {
border: 1px solid red;
}
By the time you have looped through all of the elements matching your selector and added the rules scoped to each of their identifiers, our scopedStyle() plugin is going to be returning a string of CSS text like this:
[data-LndpZGdldA="0"] {
border: 1px solid red;
}
[data-LndpZGdldA="1"] {
border: 1px solid red;
}
[data-LndpZGdldA="2"] {
border: 1px solid red;
}
So if we think back to the original stylesheet our JSinCSS() plugin was holding, this string is going to end up at the bottom of that, below the global CSS example and the global JS example.
body {
background: tan;
}
body:before {
content: '1248 x 238'
}
[data-LndpZGdldA="0"] {
border: 1px solid red;
}
[data-LndpZGdldA="1"] {
border: 1px solid red;
}
[data-LndpZGdldA="2"] {
border: 1px solid red;
}
And here's the code above running in the browser. We can see the data-LndpZGdldA attributes on the class=widget elements at the bottom, and the <style id=JSinCSS> that's being populated with the current state of our rendered CSS stylesheet. Success!

So that's a little overview of how you can implement style scoping from scratch in JS, and the parts required to make it work:
Hopefully you can figure out how to apply the same idea to your HTML here. You want to be working with CSS stylesheets and taking advantage of CSS selectors, but also taking advantage of JavaScript's smarts and everything it knows too!
If the content is a table of data, and you want to make use of table display in the browser…then why choose
<div>elements (and need to setdisplay: table-cellon each) instead of using actual<table>elements?It looks like you're trying to use CSS to recreate how a
<table>works so I wonder if a good chunk of the CSS you're struggling to apply might not even be necessary in the first place if your HTML elements match the content they are marking up more closely.To the original question - how do you avoid massive style duplication when applying CSS? By using 'CSS selectors' in a 'CSS stylesheet'. Right now you're applying these CSS properties to each element using their
style=""attribute, which makes your DOM huge, and slows everything down the more CSS you have in there. The cool thing about CSS stylesheets is you only have to write 1 rule for each set of elements you want to inherit the same style, and the browser is able to apply those styles to elements without needing to set thestyle=""attribute at all! I would highly recommend you use actual CSS stylesheets wherever possible in your site, and only use CSS-in-JS for values that depend on JavaScript logic to work.A question for you and your workflow: How would you (in your particular CSS-in-JS workflow) add a style for when the mouse is hovering one of those elements? Are you able to write a CSS selector with
:hover, or does it require JavaScript to addmouseoverandmouseoutevent listeners to each of those elements as well?