CSS pseudo elements in React
Asked Answered
L

8

118

I'm building React components. I have added CSS inline in the components as suggested in this brilliant presentation by one of the people behind React. I've been trying all night to find a way to add CSS pseudo classes inline, like on the slide titled "::after" in the presentation. Unfortunately, I do not just need to add the content:""; property, but also position:absolute; -webkit-filter: blur(10px) saturate(2);. The slides show how to add content through {/* … */}, but how would you add other properties?

Lioness answered 2/2, 2015 at 0:42 Comment(0)
L
107

Got a reply from @Vjeux over at the React team:

Normal HTML/CSS:

<div class="something"><span>Something</span></div>
<style>
    .something::after {
    content: '';
    position: absolute;
    -webkit-filter: blur(10px) saturate(2);
}
</style>

React with inline style:

render: function() {
    return (
        <div>
          <span>Something</span>
          <div style={{position: 'absolute', WebkitFilter: 'blur(10px) saturate(2)'}} />
        </div>
    );
},

The trick is that instead of using ::after in CSS in order to create a new element, you should instead create a new element via React. If you don't want to have to add this element everywhere, then make a component that does it for you.

For special attributes like -webkit-filter, the way to encode them is by removing dashes - and capitalizing the next letter. So it turns into WebkitFilter. Note that doing {'-webkit-filter': ...} should also work.

Lioness answered 2/2, 2015 at 1:27 Comment(10)
Obligatory sorry for answering my own question. And thanks to @Vjeux for his answer. It worked really well!Lioness
But, as React JS is rendered out to search agents, they will index the pseudo content, which isn't good. If you, for example, copy a headline into a ::after-element, to create a custom shadow, just for styling purposes I wouldn't be in the DOM when Google sees it, but with this approach, Google will see it... Meaning you will have two headlines.Fount
Too bad this pops-up on top when you search for pseudo-elements in react. @vjeux might be quite annoyed to have said that css was bad, without actually providing a clean/complete alternative to its react-followers. This solution should not be accepted, it is a step backward — btw sure it did work really well ahahHolierthanthou
@Ben: I checked with Vjeux before posting this. Nowhere in this question or answer does it say that he said CSS was bad. Keep in mind that this solution will probably change as new builds of React are released. Until then I'm glad there is a solution (or in this case, workaround) that is simple and works well.Lioness
sorry for being a big sarcastic on my comment, but I was amazed to discover those thing were missing, and that he promoted this type use right here. I might just be lost in the state of React — production ready or experimental-in-progress-tool. waiting for new builds thenHolierthanthou
This is not a proper solution in my opinion. I use pseudo element like before and after to avoid creating unnecessary DOMs.Quip
There's a presentation by Vjeux in which he lists flaws of CSS and "fixes" them with React's inline styles. The whole "CSS sucks and our thing is great." nature of the presentation is very harmful and confuses a lot of newcomers. @KennethB's argument is valid and there are more valid arguments for using CSS with React. CSS has a nice, declarative syntax just like React, but using them together is harder than it needs to be and the team puts almost no effort into making it easier. I hope they eat the humble pie one day and fix these issues. Stopping the hate on CSS would be a nice first step.Encamp
I have been facing this issue from a long time I have a backgroundImage - .backgroundImageProp::after{ background-image: url('republic-prod.azureedge.net/republic-prod/stories/promolarge/…); background-repeat: no-repeat; } I want to pass the background-image dynamically, I have tried lot of options - <section className="section parallax backgroundImageProp" style={{backgroundImage:'url(' + image_url + ')' ,after:'url(' + image_url + ')'}}></section> but didnt help, please let me know how can I achieve this, I am new to reactDalhousie
This does not answer the question, creating a new element instead of useding pseudo elements is not a sufficient solutionScuttlebutt
I had the same issue i was using the -webkit-user-select: "text"​ then I saw react is not accepting this as a style so I found that i should use webkitUserSelectRoselba
M
51

Inline styles cannot be used to target pseudo-classes or pseudo-elements. You need to use a stylesheet.

If you want to generate CSS dynamically, then the easiest way is to create a DOM element <style>.

<style
  dangerouslySetInnerHTML={{
    __html: `
      .my-special-div:after {
        content: "Hello";
        position: absolute
      }
    `
  }}
></style>
<div className="my-special-div"></div>
Malleolus answered 2/2, 2015 at 1:24 Comment(1)
Nice. Even better would be to just use a template string with literal newlines so you don't need to join a bunch of individual strings.Precisian
R
29

Depending if you only need a couple attributes to be styled inline you can do something like this solution (and saves you from having to install a special package or create an extra element):

https://stackoverflow.com/a/42000085

<span class="something" datacustomattribute="👋">
  Hello
</span>
.something::before {
  content: attr(datascustomattribute);
  position: absolute;
}

Note that the datacustomattribute must start with data and be all lowercase to satisfy React.

Rapture answered 25/2, 2020 at 22:44 Comment(4)
Also note that, whilst it has to be lowercase, it can also include dashes, e.g. data-custom-attribute works tooSelfinterest
As of July 2021, as stated in MDN docs: The attr() function can be used with any CSS property, but support for properties other than content is experimental, and support for the type-or-unit parameter is sparse.Rikkiriksdag
works well with reactStriation
It works perfectly well for me. Thanks Peter :)Zurn
V
17

Inline styling does not support pseudos or at-rules (e.g., @media). Recommendations range from reimplement CSS features in JavaScript for CSS states like :hover via onMouseEnter and onMouseLeave to using more elements to reproduce pseudo-elements like :after and :before to just use an external stylesheet.

Personally dislike all of those solutions. Reimplementing CSS features via JavaScript does not scale well -- neither does adding superfluous markup.

Imagine a large team wherein each developer is recreating CSS features like :hover. Each developer will do it differently, as teams grow in size, if it can be done, it will be done. Fact is with JavaScript there are about n ways to reimplement CSS features, and over time you can bet on every one of those ways being implemented with the end result being spaghetti code.

So what to do? Use CSS. Granted you asked about inline styling going to assume you're likely in the CSS-in-JS camp (me too!). Have found colocating HTML and CSS to be as valuable as colocating JS and HTML, lots of folks just don't realise it yet (JS-HTML colocation had lots of resistance too at first).

Made a solution in this space called Style It that simply lets your write plaintext CSS in your React components. No need to waste cycles reinventing CSS in JS. Right tool for the right job, here is an example using :after:

npm install style-it --save

Functional Syntax (JSFIDDLE)

import React from 'react';
import Style from 'style-it';

class Intro extends React.Component {
  render() {
    return Style.it(`
      #heart {
        position: relative;
        width: 100px;
        height: 90px;
      }
      #heart:before,
      #heart:after {
        position: absolute;
        content: "";
        left: 50px;
        top: 0;
        width: 50px;
        height: 80px;
        background: red;
        -moz-border-radius: 50px 50px 0 0;
        border-radius: 50px 50px 0 0;
        -webkit-transform: rotate(-45deg);
        -moz-transform: rotate(-45deg);
        -ms-transform: rotate(-45deg);
        -o-transform: rotate(-45deg);
        transform: rotate(-45deg);
        -webkit-transform-origin: 0 100%;
        -moz-transform-origin: 0 100%;
        -ms-transform-origin: 0 100%;
        -o-transform-origin: 0 100%;
        transform-origin: 0 100%;
      }
      #heart:after {
        left: 0;
        -webkit-transform: rotate(45deg);
        -moz-transform: rotate(45deg);
        -ms-transform: rotate(45deg);
        -o-transform: rotate(45deg);
        transform: rotate(45deg);
        -webkit-transform-origin: 100% 100%;
        -moz-transform-origin: 100% 100%;
        -ms-transform-origin: 100% 100%;
        -o-transform-origin: 100% 100%;
        transform-origin :100% 100%;
      }
    `,
      <div id="heart" />
    );
  }
}

export default Intro;

JSX Syntax (JSFIDDLE)

import React from 'react';
import Style from 'style-it';

class Intro extends React.Component {
  render() {
    return (
      <Style>
      {`
        #heart {
          position: relative;
          width: 100px;
          height: 90px;
        }
        #heart:before,
        #heart:after {
          position: absolute;
          content: "";
          left: 50px;
          top: 0;
          width: 50px;
          height: 80px;
          background: red;
          -moz-border-radius: 50px 50px 0 0;
          border-radius: 50px 50px 0 0;
          -webkit-transform: rotate(-45deg);
          -moz-transform: rotate(-45deg);
          -ms-transform: rotate(-45deg);
          -o-transform: rotate(-45deg);
          transform: rotate(-45deg);
          -webkit-transform-origin: 0 100%;
          -moz-transform-origin: 0 100%;
          -ms-transform-origin: 0 100%;
          -o-transform-origin: 0 100%;
          transform-origin: 0 100%;
        }
        #heart:after {
          left: 0;
          -webkit-transform: rotate(45deg);
          -moz-transform: rotate(45deg);
          -ms-transform: rotate(45deg);
          -o-transform: rotate(45deg);
          transform: rotate(45deg);
          -webkit-transform-origin: 100% 100%;
          -moz-transform-origin: 100% 100%;
          -ms-transform-origin: 100% 100%;
          -o-transform-origin: 100% 100%;
          transform-origin :100% 100%;
        }
     `}

      <div id="heart" />
    </Style>
  }
}

export default Intro;

Heart example pulled from CSS-Tricks

Vitavitaceous answered 8/9, 2016 at 5:58 Comment(1)
style-it is not maintained. Don't install.Jaquenette
W
13

You can use styled components.

Install it with npm i styled-components

import React from 'react';
import styled from 'styled-components';

const ComponentWithPseudoClass = styled.div`
  height: 50px;
  position: relative;
  &:after {
    // whatever you want with normal CSS syntax. Here, a custom orange line as example
    content: '';
    width: 60px;
    height: 4px;
    background: orange
    position: absolute;
    bottom: 0;
    left: 0;
  }
`;

const YourComponent = props => {
  return (
    <ComponentWithPseudoClass>...</ComponentWithPseudoClass>
  )
}

export default YourComponent
Willyt answered 11/11, 2019 at 10:35 Comment(0)
G
4

I don't know if this would be considered hacky but it certainly works (using CSS variable):

const passedInlineStyle = { '--color':'blue'}

Then in an imported CSS file:

background:var(--color);
Gstring answered 9/4, 2021 at 21:39 Comment(0)
S
0

The easiest trick is to add '""' instead of '' in the content property for the pseudo-element.

const styles = {
    container: {
        // ... Container styling

        '&::after': {
            display: 'block',
            content: '""'
        }
    }
};
Sawbuck answered 5/2, 2023 at 5:17 Comment(0)
H
-2

Not a direct answer to the question, but this may help those who are having trouble creating style information using Typescript.

I was getting an error telling me that the following was incorrect:

let iconStyle = {
    position: 'relative',
    maxHeight: '90px',
    top: '25%',
}

The error told me that "types of property 'position' are incompatible". I have no idea why.

I fixed this by adding a strict Typescript declaration, like so:

let iconStyle: CSSProperties = {
    position: 'relative',
    maxHeight: '90px',
    top: '25%',
}

This works.

Hemihedral answered 2/3, 2019 at 5:1 Comment(5)
Question literally has nothing to do with TypeScript.Punctate
@Punctate Yes it does. The given answer throws an error when written in Typescript. I provided a solution to that error.Hemihedral
Your answer is about how to type CSS properties, specifically a position property type and why you can't use a string. There are a few solutions to this (and you suggest a good one), but again, this question isn't about TypeScript. It's about JavaScript and React and CSS. No one coming here should think TypeScript has anything to do with this question or its given solution.Punctate
@Punctate Surely you're aware of how much overlap Javascript and Typescript have? There are countless times I (and surely others) have searched for solutions to issues when working with React, and found a solution written in Javascript which works just as well in Typescript. Sometimes however, such as the above, this isn't the case. For me to add a note with a small tweak to make the above solution work for those who came here with React CSS issues in Typescript is hardly a leap into the arbitrary. I don't see at all what your big problem is here to be honest.Hemihedral
My "big problem" is that your answer isn't helpful to someone not using TypeScript. Currently, your answer sits above the accepted answer. However, it doesn't answer the question - you say so yourself. It answers a possible issue some users may have which is tangential to the question. What happens when someone is using SASS in their pipeline, should we answer that, too? What about React Native? One could argue that's as tangential to React as TypeScript. See my point?Punctate

© 2022 - 2024 — McMap. All rights reserved.