218
votes

I quite like the inline CSS pattern in React and decided to use it.

However, you can't use the :hover and similar selectors. So what's the best way to implement highlight-on-hover while using inline CSS styles?

One suggestion from #reactjs is to have a Clickable component and use it like this:

<Clickable>
    <Link />
</Clickable>

The Clickable has a hovered state and passes it as props to the Link. However, the Clickable (the way I implemented it) wraps the Link in a div so that it can set onMouseEnter and onMouseLeave to it. This makes things a bit complicated though (e.g. span wrapped in a div behaves differently than span).

Is there a simpler way?

23
You're absolutely right - the only way to simulate :hover etc selectors with inline styles is to use onMouseEnter and onMouseLeave. Regarding the exact implementation of that - it's entirely up to you. To look at your specific example, why not make the <Clickable/> wrapper a span?Chris Houghton
i would suggest using external style sheets along with ExtractText Webpack plugin , this will help you on longer run if you ever wish to ServerRender otherwise you can try Radium github.com/FormidableLabs/radiumabhirathore2006
Currently Styled Component is best solution to simulate all possibilities of css/scss in react.Ahmad Behzadi

23 Answers

52
votes

I'm in the same situation. Really like the pattern of keeping the styling in the components but the hover states seems like the last hurdle.

What I did was writing a mixin that you can add to your component that needs hover states. This mixin will add a new hovered property to the state of your component. It will be set to true if the user hovers over the main DOM node of the component and sets it back to false if the users leaves the element.

Now in your component render function you can do something like:

<button style={m(
     this.styles.container,
     this.state.hovered && this.styles.hover,
)}>{this.props.children}</button>

Now each time the state of the hovered state changes the component will rerender.

I've also create a sandbox repo for this that I use to test some of these patterns myself. Check it out if you want to see an example of my implementation.

https://github.com/Sitebase/cssinjs/tree/feature-interaction-mixin

121
votes

I think onMouseEnter and onMouseLeave are the ways to go, but I don't see the need for an additional wrapper component. Here is how I implemented it:

var Link = React.createClass({
  getInitialState: function(){
    return {hover: false}
  },
  toggleHover: function(){
    this.setState({hover: !this.state.hover})
  },
  render: function() {
    var linkStyle;
    if (this.state.hover) {
      linkStyle = {backgroundColor: 'red'}
    } else {
      linkStyle = {backgroundColor: 'blue'}
    }
    return(
      <div>
        <a style={linkStyle} onMouseEnter={this.toggleHover} onMouseLeave={this.toggleHover}>Link</a>
      </div>
    )
}

You can then use the state of hover (true/false) to change the style of the link.

79
votes

Late to party but come with solution. You can use "&" to defines styles for hover nth Child etc:

day: {
    display: "flex",
    flex: "1",
    justifyContent: "center",
    alignItems: "center",
    width: "50px",
    height: "50px",
    transition: "all 0.2s",
    borderLeft: "solid 1px #cccccc",

    "&:hover": {
      background: "#efefef"
    },
    "&:last-child": {
      borderRight: "solid 1px #cccccc"
    }
},
27
votes

You can use Radium - it is an open source tool for inline styles with ReactJS. It adds exactly the selectors you need. Very popular, check it out - Radium on npm

12
votes

Full CSS support is exactly the reason this huge amount of CSSinJS libraries, to do this efficiently, you need to generate actual CSS, not inline styles. Also inline styles are much slower in react in a bigger system. Disclaimer - I maintain JSS.

12
votes

Here's my solution using React Hooks. It combines the spread operator and the ternary operator.

style.js

export default {
  normal:{
    background: 'purple',
    color: '#ffffff'
  },
  hover: {
    background: 'red'
  }
}

Button.js

import React, {useState} from 'react';
import style from './style.js'

function Button(){

  const [hover, setHover] = useState(false);

  return(
    <button
      onMouseEnter={()=>{
        setHover(true);
      }}
      onMouseLeave={()=>{
        setHover(false);
      }}
      style={{
        ...style.normal,
        ...(hover ? style.hover : null)
      }}>

        MyButtonText

    </button>
  )
}
11
votes

Made Style It -- in part -- because of this reason (others being disagreements with implementation of other libs / syntax and inline stylings lack of support for prefixing property values). Believe we should be able to simply write CSS in JavaScript and have fully self contained components HTML-CSS-JS. With ES5 / ES6 template strings we now can and it can be pretty too! :)

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(`
      .intro:hover {
        color: red;
      }
    `,
      <p className="intro">CSS-in-JS made simple -- just Style It.</p>
    );
  }
}

export default Intro;

JSX Syntax (JSFIDDLE)

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

class Intro extends React.Component {
  render() {
    return (
      <Style>
      {`
        .intro:hover {
          color: red;
        }
      `}

        <p className="intro">CSS-in-JS made simple -- just Style It.</p>
      </Style>
    );
  }
}

export default Intro;
8
votes

Adding on to Jonathan's answer, here are the events to cover the focus and active states, and a using onMouseOver instead of onMouseEnter since the latter will not bubble if you have any child elements within the target the event is being applied to.

var Link = React.createClass({

  getInitialState: function(){
    return {hover: false, active: false, focus: false}
  },

  toggleHover: function(){
    this.setState({hover: !this.state.hover})
  },

  toggleActive: function(){
    this.setState({active: !this.state.active})
  },

  toggleFocus: function(){
    this.setState({focus: !this.state.focus})
  },

  render: function() {
    var linkStyle;
    if (this.state.hover) {
      linkStyle = {backgroundColor: 'red'}
    } else if (this.state.active) {
      linkStyle = {backgroundColor: 'blue'}
    } else if (this.state.focus) {
      linkStyle = {backgroundColor: 'purple'}
    } 

    return(
      <div>
        <a style={linkStyle} 
          onMouseOver={this.toggleHover} 
          onMouseOut={this.toggleHover} 
          onMouseUp={this.toggleActive} 
          onMouseDown={this.toggleActive} 
          onFocus={this.toggleFocus}> 
          Link 
        </a>
      </div>
    )
  }
6
votes

In regards to styled-components and react-router v4 you can do this:

import {NavLink} from 'react-router-dom'

const Link = styled(NavLink)`     
  background: blue;

  &:hover {
    color: white;
  }
`

...
<Clickable><Link to="/somewhere">somewhere</Link></Clickable>
6
votes

This can be a nice hack for having inline style inside a react component (and also using :hover CSS function):

...

<style>
  {`.galleryThumbnail.selected:hover{outline:2px solid #00c6af}`}
</style>

...

5
votes

Checkout Typestyle if you are using React with Typescript.

Below is a sample code for :hover

import {style} from "typestyle";

/** convert a style object to a CSS class name */
const niceColors = style({
  transition: 'color .2s',
  color: 'blue',
  $nest: {
    '&:hover': {
      color: 'red'
    }
  }
});

<h1 className={niceColors}>Hello world</h1>
5
votes

The simple way is using ternary operator

var Link = React.createClass({
  getInitialState: function(){
    return {hover: false}
  },
  toggleHover: function(){
    this.setState({hover: !this.state.hover})
  },
  render: function() {
    var linkStyle;
    if (this.state.hover) {
      linkStyle = {backgroundColor: 'red'}
    } else {
      linkStyle = {backgroundColor: 'blue'}
    }
    return(
      <div>
        <a style={this.state.hover ? {"backgroundColor": 'red'}: {"backgroundColor": 'blue'}} onMouseEnter={this.toggleHover} onMouseLeave={this.toggleHover}>Link</a>
      </div>
    )
  }
4
votes

You can use css modules as an alternative, and additionally react-css-modules for class name mapping.

That way you can import your styles as follows and use normal css scoped locally to your components:

import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './table.css';

class Table extends React.Component {
    render () {
        return <div styleName='table'>
            <div styleName='row'>
                <div styleName='cell'>A0</div>
                <div styleName='cell'>B0</div>
            </div>
        </div>;
    }
}

export default CSSModules(Table, styles);

Here is a webpack css modules example

4
votes

onMouseOver and onMouseLeave with setState at first seemed like a bit of overhead to me - but as this is how react works, it seems the easiest and cleanest solution to me.

rendering a theming css serverside for example, is also a good solution and keeps the react components more clean.

if you dont have to append dynamic styles to elements ( for example for a theming ) you should not use inline styles at all but use css classes instead.

this is a traditional html/css rule to keep html / JSX clean and simple.

2
votes

This can be easily achived with material-ui makeStyles invocation:

import { makeStyles } from '@material-ui/core/styles';

makeStyles({
  root: {
    /* … */
    '&:hover': { /* … */ }
  },
});
1
votes

With a using of the hooks:

const useFade = () => {
  const [ fade, setFade ] = useState(false);

  const onMouseEnter = () => {
    setFade(true);
  };

  const onMouseLeave = () => {
    setFade(false);
  };

  const fadeStyle = !fade ? {
    opacity: 1, transition: 'all .2s ease-in-out',
  } : {
    opacity: .5, transition: 'all .2s ease-in-out',
  };

  return { fadeStyle, onMouseEnter, onMouseLeave };
};

const ListItem = ({ style }) => {
  const { fadeStyle, ...fadeProps } = useFade();

  return (
    <Paper
      style={{...fadeStyle, ...style}}
      {...fadeProps}
    >
      {...}
    </Paper>
  );
};
1
votes

This is a universal wrapper for hover written in typescript. The component will apply style passed via props 'hoverStyle' on hover event.

import React, { useState } from 'react';

export const Hover: React.FC<{
  style?: React.CSSProperties;
  hoverStyle: React.CSSProperties;
}> = ({ style = {}, hoverStyle, children }) => {
  const [isHovered, setHovered] = useState(false);
  const calculatedStyle = { ...style, ...(isHovered ? hoverStyle : {}) };
  return (
    <div
      style={calculatedStyle}
      onMouseEnter={() => setHovered(true)}
      onMouseLeave={() => setHovered(false)}
    >
      {children}
    </div>
  );
};    
0
votes

I use a pretty hack-ish solution for this in one of my recent applications that works for my purposes, and I find it quicker than writing custom hover settings functions in vanilla js (though, I recognize, maybe not a best practice in most environments..) So, in case you're still interested, here goes.

I create a parent element just for the sake of holding the inline javascript styles, then a child with a className or id that my css stylesheet will latch onto and write the hover style in my dedicated css file. This works because the more granular child element receives the inline js styles via inheritance, but has its hover styles overridden by the css file.

So basically, my actual css file exists for the sole purpose of holding hover effects, nothing else. This makes it pretty concise and easy to manage, and allows me to do the heavy-lifting in my in-line React component styles.

Here's an example:

const styles = {
  container: {
    height: '3em',
    backgroundColor: 'white',
    display: 'flex',
    flexDirection: 'row',
    alignItems: 'stretch',
    justifyContent: 'flex-start',
    borderBottom: '1px solid gainsboro',
  },
  parent: {
    display: 'flex',
    flex: 1,
    flexDirection: 'row',
    alignItems: 'stretch',
    justifyContent: 'flex-start',
    color: 'darkgrey',
  },
  child: {
    width: '6em',
    textAlign: 'center',
    verticalAlign: 'middle',
    lineHeight: '3em',
  },
};

var NavBar = (props) => {
  const menuOptions = ['home', 'blog', 'projects', 'about'];

  return (
    <div style={styles.container}>
      <div style={styles.parent}>
        {menuOptions.map((page) => <div className={'navBarOption'} style={styles.child} key={page}>{page}</div> )}
      </div>
    </div>
  );
};


ReactDOM.render(
  <NavBar/>,
  document.getElementById('app')
);
.navBarOption:hover {
  color: black;
}
<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>

<div id="app"></div>

Notice that the "child" inline style does not have a "color" property set. If it did, this would not work because the inline style would take precedence over my stylesheet.

0
votes

I'm not 100% sure if this is the answer, but its the trick i use to simulate the CSS :hover effect with colours and images inline.

`This works best with an image`

class TestHover extends React.PureComponent {
render() {
const landingImage = {     
"backgroundImage": "url(https://i.dailymail.co.uk/i/pix/2015/09/01/18/2BE1E88B00000578-3218613-image-m-5_1441127035222.jpg)",
"BackgroundColor": "Red", `this can be any color`
"minHeight": "100%",
"backgroundAttachment": "fixed",
"backgroundPosition": "center",
"backgroundRepeat": "no-repeat",
"backgroundSize": "cover", 
"opacity": "0.8", `the hove trick is here in the opcaity slightly see through gives the effect when the background color changes`
    }

  return (
    <aside className="menu">
        <div className="menu-item">
          <div style={landingImage}>SOME TEXT</div>
        </div>
    </aside>
      ); 
  }
}
ReactDOM.render(
    <TestHover />,
  document.getElementById("root")
);

CSS:

.menu {
top: 2.70em;
bottom: 0px;
width: 100%;
position: absolute;
}

.menu-item {
cursor: pointer;
height: 100%;
font-size: 2em;
line-height: 1.3em;
color: #000;
font-family: "Poppins";
font-style: italic;
font-weight: 800;
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
}

Before hover

.menu-item:nth-child(1) {
color: white;
background-color: #001b37;
} 

On hover

.menu-item:nth-child(1):hover {
color: green;
background-color: white;
}

Example: https://codepen.io/roryfn/pen/dxyYqj?editors=0011

0
votes
<Hoverable hoverStyle={styles.linkHover}>
  <a href="https://example.com" style={styles.link}>
    Go
  </a>
</Hoverable>

Where Hoverable is defined as:

function Hoverable(props) {
  const [hover, setHover] = useState(false);

  const child = Children.only(props.children);

  const onHoverChange = useCallback(
    e => {
      const name = e.type === "mouseenter" ? "onMouseEnter" : "onMouseLeave";
      setHover(!hover);
      if (child.props[name]) {
        child.props[name](e);
      }
    },
    [setHover, hover, child]
  );

  return React.cloneElement(child, {
    onMouseEnter: onHoverChange,
    onMouseLeave: onHoverChange,
    style: Object.assign({}, child.props.style, hover ? props.hoverStyle : {})
  });
}
0
votes

Heres is another option using CSS variables . This requires a css hover definition ahead of time so I guess its not pure inline, but is very little code and flexible.

css (setup a hover state) :

.p:hover:{
 color:var(--hover-color) !important,
 opacity:var(--hover-opacity)
}

react:

<p style={{'color':'red','--hover-color':'blue','--hover-opacity':0.5}}>
0
votes

I use this trick, a mix between inline-style and css:

//inline-style:
const button = {
  fontSize: "2em",
};
return (
  <div style={button} data-hover="button">
    <style>{`[data-hover="button"]:hover {
        font-size: 2.1em !important;
    }`}</style>
    {this.props.text}
  </div>
);
0
votes

I did something similar to this, but I do not use material-ui or makeStyles. I added the hover as a condition in my css in a style object:

const styles = {
  hoverStyle: {
    color: 'grey',
    '&:hover': { color: 'blue !important' },
  }
};

var NavBar = (props) => {
  const menuOptions = ['home', 'blog', 'projects', 'about'];

  return (
    <div>
      <div>
        {menuOptions.map((page) => <div style={styles.hoverStyle} key={page}>{page}</div> )}
      </div>
    </div>
  );
};

This worked for me.