I'm taking my first steps into web components without using any third-party libraries, such as Polymer. One of the main selling points is that web component styles are separated from styles defined elsewhere, allowing the component's shadow-DOM to be styled in a sandbox-like environment.
The issue I'm running into is how styles cascade through slotted elements. Since slotted elements are not part of the shadow DOM, they can only be targed with the ::slotted() selector within the component template. This is great, but it makes it almost impossible to guarantee a web component will display correctly in all contexts, since externally-defined styles also apply with undefeatable specificity* to slotted elements.
*besides !important.
This issue can be distilled down to this:
customElements.define("my-nav",
class extends HTMLElement {
constructor() {
super();
const template = document.querySelector("template#my-nav").content;
this.attachShadow({ mode: "open" })
.appendChild(template.cloneNode(true));
}
}
);
a {
color: red; /* >:( */
}
<template id="my-nav">
<style>
.links-container ::slotted(a) {
color: lime;
font-weight: bold;
margin-right: 20px;
}
</style>
<div class="links-container">
<slot name="links"></slot>
</div>
</template>
<p>I want these links to be green:</p>
<my-nav>
Link 1
Link 2
Link 3
</my-nav>
I'm having a hard time understanding the value of this "feature". I either have to specify my links in some other format and create their nodes with JS, or add !important to my color property - which still doesn't guarantee consistency when it comes to literally any other property I haven't defined.
Has this issue been addressed somewhere, or is this easily solved by changing my light DOM structure? I am not sure how else to get a list of links into a slot.
The <slot> is intentionally designed to allow the outer code to style the content placed into it. This is a great feature when used correctly.
But if you want better control of what shows in the web component then you need to copy cloned copies of the content from this.childNodes into the shadow DOM. Then you have 100% control over the CSS.
OK. You really only have 90% control because the person using your component can still set the style attribute.
customElements.define("my-nav",
class extends HTMLElement {
constructor() {
super();
const template = document.querySelector("template#my-nav").content;
this.attachShadow({ mode: "open" })
.appendChild(template.cloneNode(true));
}
connectedCallback() {
var container = this.shadowRoot.querySelector('.links-container');
var children = this.childNodes;
if (children.length > 0 && container) {
while(container.firstChild) {
container.removeChild(container.firstChild);
}
for (var i = 0; i < children.length; i++) {
container.appendChild(children[i].cloneNode(true));
}
}
}
}
);
a {
color: red;
}
<template id="my-nav">
<style>
.links-container a {
color: lime;
font-weight: bold;
margin-right: 20px;
}
</style>
<div class="links-container">
</div>
</template>
<p>I want these links to be green:</p>
<my-nav>
Link 1
Link 2
Link 3
</my-nav>
As you can see in the example above the third link is still red because we set the style attribute.
If you want to prevent that from happening then you would need to strip the style attribute from the inner content.
customElements.define("my-nav",
class extends HTMLElement {
constructor() {
super();
const template = document.querySelector("template#my-nav").content;
this.attachShadow({ mode: "open" })
.appendChild(template.cloneNode(true));
}
connectedCallback() {
var container = this.shadowRoot.querySelector('.links-container');
var children = this.childNodes;
if (children.length > 0 && container) {
while(container.firstChild) {
container.removeChild(container.firstChild);
}
for (var i = 0; i < children.length; i++) {
container.appendChild(children[i].cloneNode(true));
}
container.querySelectorAll('[style]').forEach(el => el.removeAttribute('style'));
}
}
}
);
a {
color: red;
}
<template id="my-nav">
<style>
.links-container a {
color: lime;
font-weight: bold;
margin-right: 20px;
}
</style>
<div class="links-container">
</div>
</template>
<p>I want these links to be green:</p>
<my-nav>
Link 1
Link 2
Link 3
</my-nav>
I have even created some components that allow unique children that I read in and convert into custom internal nodes.
Think of the <video> tag and its <source> children. Those children don't really render anything, they are just a way of holding data that is used to indicate the source location of the video to be played.
The key here is to understand what <slot> is supposed to be used for and only use it that way without trying to force it to do something it was never intended to do.
BONUS POINTS
Since ConnectedCallback is called every time this node in placed into the DOM you have to be careful to remove anything within the shadow DOM each time or you will duplicate the children over and over.
customElements.define("my-nav",
class extends HTMLElement {
constructor() {
super();
const template = document.querySelector("template#my-nav").content;
this.attachShadow({ mode: "open" })
.appendChild(template.cloneNode(true));
}
connectedCallback() {
var container = this.shadowRoot.querySelector('.links-container');
var children = this.childNodes;
if (children.length > 0 && container) {
for (var i = 0; i < children.length; i++) {
container.appendChild(children[i].cloneNode(true));
}
}
}
}
);
function reInsert() {
var el = document.querySelector('my-nav');
var parent = el.parentNode;
el.remove();
parent.appendChild(el);
}
setTimeout(reInsert, 1000);
setTimeout(reInsert, 2000);
a {
color: red;
}
<template id="my-nav">
<style>
.links-container a {
color: lime;
font-weight: bold;
margin-right: 20px;
}
</style>
<div class="links-container">
</div>
</template>
<p>I want these links to be green:</p>
<my-nav>
Link 1
Link 2
Link 3
</my-nav>
So removing the duplicated nodes is important:
customElements.define("my-nav",
class extends HTMLElement {
constructor() {
super();
const template = document.querySelector("template#my-nav").content;
this.attachShadow({ mode: "open" })
.appendChild(template.cloneNode(true));
}
connectedCallback() {
var container = this.shadowRoot.querySelector('.links-container');
var children = this.childNodes;
if (children.length > 0 && container) {
while(container.firstChild) {
container.removeChild(container.firstChild);
}
for (var i = 0; i < children.length; i++) {
container.appendChild(children[i].cloneNode(true));
}
}
}
}
);
function reInsert() {
var el = document.querySelector('my-nav');
var parent = el.parentNode;
el.remove();
parent.appendChild(el);
}
setTimeout(reInsert, 1000);
setTimeout(reInsert, 2000);
a {
color: red;
}
<template id="my-nav">
<style>
.links-container a {
color: lime;
font-weight: bold;
margin-right: 20px;
}
</style>
<div class="links-container">
</div>
</template>
<p>I want these links to be green:</p>
<my-nav>
Link 1
Link 2
Link 3
</my-nav>
You're right, there's no solution other that using !important for every CSS property.
Instead, I would not use <slot> and copy the nodes you need:
customElements.define("my-nav",
class extends HTMLElement {
constructor() {
super();
const template = document.querySelector("template#my-nav").content;
this.attachShadow({ mode: "open" })
.appendChild(template.cloneNode(true));
}
connectedCallback() {
var links = this.querySelectorAll( 'a[slot]' )
var container = this.shadowRoot.querySelector( '.links-container' )
links.forEach( l => container.appendChild( l ) )
}
}
);
a {
color: red; /* >:( */
}
<template id="my-nav">
<style>
.links-container > a {
color: lime;
font-weight: bold;
margin-right: 20px;
}
</style>
<div class="links-container">
</div>
</template>
<p>I want these links to be green:</p>
<my-nav>
Link 1
Link 2
Link 3
</my-nav>
Related
Let's say I want to create a custom element which bolds every other character. For example, <staggered-bold>Hello</staggered-bold> would become "Hello, where the H, l, and o are all bolded.
There's no nth-letter CSS selector, so as far as I know the only way to achieve this effect is to wrap each individual character with a span programmatically. To do that, I have an implementation that clones the text content into the Shadow Dom, so that the child content as specified by the user is not changed.
Unfortunately, by doing so, something like <staggered-bold><span class="red">red</span></staggered-bold> no longer works, because by cloning the content into the Shadow Dom, the class CSS declarations for the wrapped span no longer apply.
Here's a proof-of-concept implementation, showcasing that the red and blue text are in fact not red and blue:
customElements.define('staggered-bold', class extends HTMLElement {
constructor() {
super()
this
.attachShadow({ mode: 'open' })
.appendChild(document.getElementById('staggered-bold').content.cloneNode(true))
}
connectedCallback() {
// this is a shadow dom element
const text = this.shadowRoot.getElementById('text')
this.shadowRoot.querySelector('slot').assignedNodes().forEach(node => {
const content = node.textContent.split('').map((char) => {
return `<span class="char">${char}</span>`
}).join('')
const newNode = node.nodeType === Node.TEXT_NODE ? document.createElement('span') : node.cloneNode(true)
newNode.innerHTML = content
text.appendChild(newNode)
})
}
})
.red { color: red; }
.blue { color: blue; }
<p><staggered-bold>Some text</staggered-bold></p>
<p><staggered-bold><span class="red">Red</span> <span class="blue">Blue</span></staggered-bold></p>
<template id="staggered-bold">
<style>
.hide { display: none; }
.char:nth-child(odd) {
font-weight: bold;
}
</style>
<span class="hide"><slot></slot></span>
<span id="text"></span>
</template>
My question is this: what is a good approach to styling each character in a custom element while preserving characteristics provided in the light dom?
One approach I've considered is to manipulate the light dom directly, but I have been avoiding that since I think of the light dom as being in full control of the usage-site (ie. things get complicated very quickly if external JS is manipulating the child of staggered-bold). I'm open to being convinced otherwise, especially there's no real alternative.
I've also considered cloning the content into a named slot so that the original text is preserved, and yet the content continues to live in the light dom. However, I feel like this is still kind of icky for the same reason as the previous paragraph.
You can't have the cake and eat it
Global CSS does NOT style shadowDOM (unless you use CSS properties)
Easier to not use shadowDOM at all.
With an extra safeguard: store the state so the element is properly redrawn on DOM moves.
Note: The setTimeout is always required,
because the connectedCallback fires early on the opening tag;
there is no parsed (innerHTML) DOM yet at that time.
So you have to wait for that DOM to be there.
If you do need a TEMPLATE and shadowDOM, dump the whole .innerHTML to the shadowRoot; but Global CSS still won't style it. Or <slot> it.
Do read: ::slotted CSS selector for nested children in shadowDOM slot
If you go with <slot> consider the slotchange Event
but be aware for an endless loop; changing lightDOM will trigger the slotchange Event again
<staggered-bold>Some text</staggered-bold>
<staggered-bold><span class="red">Red</span> <span class="blue">Blue</span></staggered-bold>
<style>
staggered-bold { display: block; font: 21px Arial }
staggered-bold .char:nth-child(even) { color: blue }
staggered-bold .char:nth-child(odd) { color: red; font-weight: bold }
</style>
<script>
customElements.define('staggered-bold', class extends HTMLElement {
connectedCallback() {
setTimeout(() => { // make sure innerHTML is all parsed
if (this.saved) this.innerHTML = this.saved;
else this.saved = this.innerHTML;
this.stagger();
})
}
stagger(node=this) {
if (node.children.length) {
[...node.children].forEach( n => this.stagger(n) )
} else {
node.innerHTML = node.textContent
.split('')
.map(ch => `<span class="char">${ch}</span>`)
.join('');
}
}
})
document.body.append(document.querySelector("staggered-bold"));//move in DOM
</script>
In the end I attempted a strategy I'm calling the mirror node. The idea is the custom element actually creates an adjacent node within which the split characters are placed.
The original node remains exactly as specified by the user, but is hidden from view
The mirror node actually displays the staggered bold text
The below implementation is incomplete, but gets the idea across:
class StaggeredBoldMirror extends HTMLElement {
constructor() {
super()
}
}
customElements.define('staggered-bold', class extends HTMLElement {
constructor() {
super()
this
.attachShadow({ mode: 'open' })
.appendChild(document.getElementById('staggered-bold').content.cloneNode(true))
}
connectedCallback() {
setTimeout(() => {
const mirror = new StaggeredBoldMirror()
mirror.innerHTML = this.divideIntoCharacters()
this.parentNode.insertBefore(mirror, this)
})
}
divideIntoCharacters = (node = this) => {
return [...node.childNodes].map(n => {
if (n.nodeType === Node.TEXT_NODE) {
return n.textContent
.split('')
.map(ch => `<span class="char">${ch}</span>`)
.join('')
} else {
const nn = n.cloneNode(false)
nn.innerHTML = this.divideIntoCharacters(n)
return nn.outerHTML
}
}).join('')
}
})
customElements.define('staggered-bold-mirror', StaggeredBoldMirror)
.red {
color: red;
}
.blue {
color: blue;
}
staggered-bold-mirror .char:nth-child(odd) {
font-weight: bold;
}
<p><staggered-bold>Some text</staggered-bold></p>
<p><staggered-bold><span class="red">Red</span> <span class="blue">Blue</span></staggered-bold></p>
<template id="staggered-bold">
<style>
.hide { display: none; }
</style>
<span class="hide"><slot></slot></span>
</template>
The vanilla component can be outfitted with a slotchange listener in order to rebuild its mirror whenever its inner content changes. The disconnectedCallback method can also ensure that when one node is removed, the other is too.
Of course, there are downsides to this approach, such has potentially having to also mirror events and the fact that it still manipulates the light dom.
Depending on the use case, either this or Danny's answer works.
I'm trying to write a simple HTML Custom Element for putting trees on pages, using simple code like:
<html-tree title="root">
<tree-node title="child1">
<tree-node title="leaf1"></tree-node>
<tree-node title="leaf2"></tree-node>
</tree-node>
<tree-node title="child2">
<tree-node title="leaf3"></tree-node>
<tree-node title="leaf4"></tree-node>
</tree-node>
</html-tree>
Each element is basically a shadow dom with a single unnamed <slot></slot> for putting children in, so I'd expect a nice, nested structure. Instead, if I assign the standard debug style :host>* { border:1px red solid; } to these custom elements, each element shows up on its own line, with a border around it, rather than showing them as being nested.
How do I preserve the markup-specified nesting in a way that CSS plays nice?
A snippet:
/**
* Main tree node class
*/
class GenericNode extends HTMLElement {
constructor() {
super();
this._shadow = enrich(this.attachShadow({mode: `open`}));
this._shadow.addSlot = () => this._shadow.add(create(`slot`));
if (!this.get) {
this.get = e => this.getAttribute(e);
}
this.setupDOM(this._shadow);
}
setupDOM(shadow) {
this.setStyle(`:host>* { border:1px red solid; }`)
if (this.leadIn) this.leadIn(shadow);
shadow.addSlot();
if (this.leadOut) this.leadOut(shadow);
}
setStyle(text) {
if (!this._style) {
this._style = create(`style`, text);
this._shadow.add(this._style);
} else {
this._style.textContent = text;
}
}
}
/**
* "not the root" element
*/
class Node extends GenericNode {
constructor() {
super();
}
leadIn(shadow) {
shadow.add(create(`p`, this.get(`title`)));
}
}
// register the component
customElements.define(`tree-node`, Node);
/**
* "the root" element, identical to Node, of course.
*/
class Tree extends Node {
constructor() {
super();
}
}
// register the component
customElements.define(`html-tree`, Tree);
/**
* utility functions
*/
function enrich(x) {
x.add = e => x.appendChild(e);
x.remove = e => {
if (e) x.removeChild(e);
else e.parentNode.removeChild(e);
};
x.get = e => x.getAttribute(x);
return x;
}
function find(qs) {
return Array.from(
document.querySelectorAll(qs).map(e => enrich(e))
);
}
function create(e,c) {
let x = enrich(document.createElement(e));
x.textContent = c;
return x;
};
<html-tree title="root">
<tree-node title="child1">
<tree-node title="leaf1"></tree-node>
<tree-node title="leaf2"></tree-node>
</tree-node>
<tree-node title="child2">
<tree-node title="leaf3"></tree-node>
<tree-node title="leaf4"></tree-node>
</tree-node>
</html-tree>
Turns out the default styling of a shadow dom and its content is "nothing", so to effect real nesting, you need to force display:block or be similarly explicit.
In the above code, rather than merely setting a border on the :host>*, the :host, and the <slot> also need to be explicitly marked as blocks:
setupDOM(shadow) {
this.setStyle(`
:host {
display: block;
border: 1px red solid;
}
:host > slot {
display: block;
border: 1px red solid;
margin-left: 1em;
}
`);
...
}
class MyElement extends HTMLElement {
constructor() {
super()
const shadow = this.attachShadow({mode: 'open'})
shadow.appendChild(document.createTextNode('Yadda blah'))
const span = document.createElement('span')
span.appendChild(document.createTextNode('Can I style U?'))
shadow.appendChild(span)
}
}
customElements.define('my-element', MyElement)
my-element {
border: 1px solid black;
}
span {
font-weight: bold;
}
<my-element></my-element>
As you can see, my-element is styled, however, the span used within my-element is not. Not even saying my-element span {font-weight: bold;} in the stylesheet makes any styles effective.
Is there any way to apply styles to this span?
That is the expected behavior of shadow dom. Since your HTML looks like <my-element></my-element>, you can target my-element from your css, but the span is not part of the actual dom, so you can't target it.
You can only apply styles to the span from within the shadow dom context.
There are better ways of defining css in this case, but just to make a point, the following apply styles to the span, but not to my-element:
class MyElement extends HTMLElement {
constructor() {
super()
const shadow = this.attachShadow({mode: 'open'})
shadow.appendChild(document.createTextNode('Yadda blah'))
const span = document.createElement('span')
span.appendChild(document.createTextNode('Can I style U?'))
shadow.appendChild(span)
const styleTag = document.createElement('style')
styleTag.innerHTML = `
my-element {
border: 1px solid black;
}
span {
font-weight: bold;
}
`
shadow.appendChild(styleTag)
}
}
customElements.define('my-element', MyElement)
<my-element></my-element>
You have to apply the style to the shadow DOM. A common but soon-to-be-outdated technique is to add a <style> element to the shadow DOM and set its text contents as the CSS you want to apply within the custom element.
The snippet below does just that. The style is saved to a variable, but you can place it anywhere as long you're conscious of how often its context is run. Following Intervalia's advice, it's best to apply a style to the custom element using :host.
It's also possible to add a <link> element instead, but that may cause the element to pop-in without a style (FOUC)
const myElementStyle = `
:host {
border: 1px solid black;
}
span {
font-weight: bold;
}
`
class MyElement extends HTMLElement {
constructor() {
super()
const shadow = this.attachShadow({mode: 'open'});
const style = document.createElement('style');
style.textContent = myElementStyle;
shadow.appendChild(style);
shadow.appendChild(document.createTextNode('Yadda blah'));
const span = document.createElement('span');
span.appendChild(document.createTextNode('Can I style U?'));
shadow.appendChild(span);
}
}
customElements.define('my-element', MyElement)
<my-element></my-element>
Another option is to use Constructable Stylesheets, which should soon be available in modern browsers. These let you create a stylesheet object that can embed styles or import styles from external resources.
I wrote up an answer here, so I'll just post a snippet that answers your question:
const customSheet = new CSSStyleSheet();
customSheet.replaceSync(`
:host {
border: 1px solid black;
}
span {
font-weight: bold;
}
`);
class MyElement extends HTMLElement {
constructor() {
super()
const shadow = this.attachShadow({
mode: 'open'
})
shadow.adoptedStyleSheets = [customSheet];
shadow.appendChild(document.createTextNode('Yadda blah'))
const span = document.createElement('span')
span.appendChild(document.createTextNode('Can I style U?'))
shadow.appendChild(span)
}
}
customElements.define('my-element', MyElement)
<my-element></my-element>
Consider the following:
class MyElem extends HTMLElement {};
customElements.define('my-element', MyElem);
class MyMoreSpecificElem extends MyElem {};
customElements.define('my-more-specific-element', MyMoreSpecificElem);
In the parlance of object inheritance, instances of the second class have an 'is-a' relationship with the first: a MyMoreSpecificElem is a MyElem.
This relationship is captured in JavaScript:
let subclassInstance = document.querySelector('my-more-specific-element');
let parentClass = document.querySelector('my-element').constructor;
subclassInstance instanceof parentClass; // true
But I can't think of any way to select all MyElems (including the subclass MyMoreSpecificElem) using CSS selectors. A tag selector would only get the superclass or subclass, and all of the relationship description selectors I'm aware of (e.g. ~, >) are about position in the document, not class hierarchy.
I mean, sure, I can add a CSS class in the constructor and select by that, the call to super would ensure that even subclass instances could be selected that way. But that's gnarly. Is there a way to do this in pure CSS?
Why not have the base component class add either a className value or an attribute that will be set for both the base component class and all of the sub-component classes. Then your CSS can be set based on this className value or attribute.
class MyBaseEl extends HTMLElement {
connectedCallback() {
this.classList.add('my-base-el');
this.innerHTML = '<div>Base El</div>';
}
}
customElements.define('my-base-el', MyBaseEl);
class MySubEl extends MyBaseEl {
connectedCallback() {
super.connectedCallback();
this.innerHTML = '<div>Sub El</div>';
}
}
customElements.define('my-sub-el', MySubEl);
.my-base-el {
background-color: #FF0;
display: block;
outline: 1px dashed black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.0.22/webcomponents-lite.js"></script>
<my-base-el></my-base-el>
<my-sub-el></my-sub-el>
This uses a className value to allow the CSS to get at all of the elements that are MyBaseEl or a subclass.
Or like this:
class MyBaseEl extends HTMLElement {
connectedCallback() {
this.setAttribute('my-base-el', '');
this.innerHTML = '<div>Base El</div>';
}
}
customElements.define('my-base-el', MyBaseEl);
class MySubEl extends MyBaseEl {
connectedCallback() {
super.connectedCallback();
this.innerHTML = '<div>Sub El</div>';
}
}
customElements.define('my-sub-el', MySubEl);
[my-base-el] {
background-color: #FF0;
display: block;
outline: 1px dashed black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.0.22/webcomponents-lite.js"></script>
<my-base-el></my-base-el>
<my-sub-el></my-sub-el>
This uses an attribute to allow the CSS to get at all of the elements that are MyBaseEl or a subclass.
UPDATE
The only other way of setting CSS for multiple element tags is to know what all of them are and make the css work for all of them like this:
my-base-el, my-sub-el, my-other-sub-el, the-third-sub-el {
background: #000;
color: #fff;
}
But, if someone else can create additional sub-classes then you would need to add to the list.
UPDATE 2
Why not just set the :host in the base class and have it use a CSS variable for the styles you want the user to be able to change. Then everything will inherit that :host css and display like you want.
I have a react component that contains inline-block div items each with a border right to provide a 'divider' visual. However, if it reaches the max width of the parent component, whether through initial loading or further resizing of the window, the last item of that line should not have a border right. It should look something like this:
Item 1 | Item 2 | Item 3 | Item 4
Item 5 | Item 6 | Item 7
I've read about using jquery to detect when an item has a change of offsetTop value but I'm not sure how it will interact with a react component. Any guidance is appreciated.
Well, the trick here is to use ref to get left offset of the item, if the left offset of the item is 0 then add leftmost class to it. The calculation is done after component has been mounted (in componentDidMount method).
And also, I add version props that is incremented on every window resize (debounced to avoid excessive update, you can lower the timeout or remove it altogether if you want) to force recalculation of the state of the border when browser window is resized.
Make sure to run the demo at full page mode to see what happen when browser window is resized.
class Item extends React.Component {
constructor(props){
super(props);
this.state = {
isLeftMost: false
};
this.recalculateBorder = () => {
if(this.el){
this.setState({isLeftMost: this.el.offsetLeft === 0});
}
}
}
componentDidMount(){
this.recalculateBorder();
}
componentDidUpdate(){
this.recalculateBorder();
}
shouldComponentUpdate(nextProps, nextState){
return (nextProps.label !== this.props.label)
|| (nextProps.version !== this.props.version)
|| (nextState.isLeftMost !== this.state.isLeftMost);
}
render(){
let cl = this.state.isLeftMost ? "item leftmost" : "item";
return (
<div className={cl} ref={el => this.el = el}>{this.props.label}</div>
);
}
}
class Container extends React.Component {
constructor(props){
super(props);
this.state = { version: 0 };
let _updateDimensions = () => {
this.setState({version: this.state.version+1 });
}
this.updateDimensions = _.debounce(_updateDimensions, 25);
}
componentDidMount() {
window.addEventListener("resize", this.updateDimensions);
}
componentWillUnmount() {
window.removeEventListener("resize", this.updateDimensions);
}
render(){
let items = [];
for(let i = 0; i<30; i++){
items.push(<Item key={i} label={"item " + i} version={this.state.version} />);
}
return (
<div className="container">
{items}
</div>
);
}
}
ReactDOM.render(<Container />, document.getElementById("root"));
.container {
position: relative;
}
.item {
display: inline-block;
padding: 0 5px;
border-left: solid 2px red;
}
.item.leftmost {
border-left-color: transparent;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<div id="root"></div>
You can use css:
:nth-last-child {
border-right-style: none;
}