Style parent component on input focus of child component in styled-components

13.9k Views Asked by At

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';
  }
`;
4

There are 4 best solutions below

3
On BEST ANSWER

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

1
On

// update 2022:

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.

1
On

import styled from "styled-components";

const MenuBtn = () => {
    const Title = styled.div`
        font-size: 16px;
    `;
    const Span = styled.span`
        font-size: 12px;
    `;
    const Button = styled.button`
        background: var(--white, #fff);
        &:hover {
            background: var(--white, #f9fbfb);
        }
        &:focus {
            border: 1px solid var(--primary-500, #4623e9);
            ${Title} {
                color: var(--primary-500, #4623e9);
            }
            ${Span} {
                color: #fff;
            }
        }
    `;

    return (
        <Button>
            <Title>title</Title>
            <Span>12</Span>
        </Button>
    );
}    

0
On

@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;
  }