3

I've seen some burdensome solutions to this problem, using refs or event handlers in React. I'm wondering if there's a solution at all in styled-components.

The code below is clearly incorrect. I'm trying to figure out the easiest way to style my parent component, when my input child component has focus. Is this possible using styled-components?

What's the best way to go about this, specifically with styled-components in mind, even if it means relying on one of the React methods mentioned above?

const Parent = () => (
  <ParentDiv>
    <Input/>
  </ParentDiv>
);


const ParentDiv = styled.div`
  background: '#FFFFFF';

  ${Input}:focus & {
    background: '#444444';
  }
`;

const Input = styled.input`
  color: #2760BC;

  &:focus{
    color: '#000000';
  }
`;
twils0
  • 1,609
  • 10
  • 22

3 Answers3

8

Check out :focus-within! I think it's exactly what you're looking for.

https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-within

hoodsy
  • 824
  • 2
  • 11
  • 18
3

// update 2018:

If you don't need Edge or IE support you can use :focus-within (thanks for figuring out @hoodsy)

div:focus-within {
  background: #444;
}
<div>
  <input type="text" />
</div>

// original answer (with IE and Edge support):

Sadly there is no way of selecting the parent, based only on the child's state with pure CSS/styled-components. Although it is a working draft in CSS4, currently no browsers support it. More about this here.

I usually add an onFocus and onBlur attribute to the input field which then triggers a state change. In your case you have a stateless component. Therefore you could use innerRef to add or remove a class from the parent. But I guess you have found this solution already. Nevertheless I'll post it as well:

const styled = styled.default;

const changeParent = ( hasFocus, ref ) => {
  // IE11 does not support second argument of toggle, so...
  const method = hasFocus ? 'add' : 'remove';
  ref.parentElement.classList[ method ]( 'has-focus' );
}

const Parent = () => {
  let inputRef = null;
  
  return (
    <ParentDiv>
      <Input
        innerRef={ dom => inputRef = dom }
        onFocus={ () => changeParent( true, inputRef ) }
        onBlur={ () => changeParent( false, inputRef ) }
      />
    </ParentDiv>
  );
};

const ParentDiv = styled.div`
  background: #fff;

  &.has-focus {
    background: #444;
  }
`;

const Input = styled.input`
  color: #2760BC;

  &:focus{
    color: #000;
  }
`;

ReactDOM.render( <Parent />, document.getElementById( 'app' ) );
<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://unpkg.com/styled-components/dist/styled-components.min.js"></script>

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

A more rigid method is to convert Parent to a class, so you'd be able to use states:

const styled = styled.default;

class Parent extends React.Component {
  state = {
    hasFocus: false,
  }
  
  setFocus = ( hasFocus ) => {
    this.setState( { hasFocus } );
  }
  
  render() {
    return (
      <ParentDiv hasFocus={ this.state.hasFocus }>
        <Input
          onFocus={ () => this.setFocus( true )  }
          onBlur={ () => this.setFocus( false ) }
        />
      </ParentDiv>
    );
  }
};

const ParentDiv = styled.div`
  background: ${ props => props.hasFocus ? '#444' : '#fff' };

`;

const Input = styled.input`
  color: #2760BC;

  &:focus{
    color: #000;
  }
`;

ReactDOM.render( <Parent />, document.getElementById( 'app' ) );
<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://unpkg.com/styled-components/dist/styled-components.min.js"></script>

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

This way you are in better control of your component and can decide more easily to pass the focus/blur state to other elements.

lumio
  • 6,606
  • 3
  • 30
  • 50
1

@hoodsy thanks,it worked like a charm I used like below on the parent div to change color of a label when an input focused .

     &:focus-within label{
      color: blue;
  }
atmosfer
  • 97
  • 1
  • 8