2

I have a React Header component that I style with styled-components. I have a selector that I use to select the next sibling to the header and give it a top-padding to keep a clear space under the header because it has position: fixed. When I use normal values for this style it works just fine, but as soon as I use CSS variables so I can customize the height with media queries the variable is no longer being parsed in the sibling element. Here is my code:

export const S_Header = styled.header<HeaderProps>`
  --height: 20vw;

  position: fixed;
  top: 0;

  width: 100vw;
  height: var(--height);

  /* Give the next sibling a top padding so text doesn't flow underneath the header */
  + * {
    padding-top: var(--height);
    margin-top: 0;
    border-top: 0;
  }
`;

The styling is getting applied to the header itself ( the height is working just normally ), but the padding-top on the sibling has value var(--height) in Chrome which is normal but it doesn't show any computed value when I hover it.

JensM
  • 143
  • 1
  • 11
  • why not just give that sibling it's own class or id and style it directly? also i haven't i am not sure what the `+` is for but normally i would use `&` so maybe you need something like `&*` just a guess – HolyMoly Nov 24 '19 at 18:04
  • 2
    CSS custom properties work when cascading. What this means is that it'll work on the element in which it's declared and that elements descendants. A sibling is not a descendant, thus you're faced with this issue. If you really want to use css custom properties, declare the property inside the `:root` pseudo element (use `createGlobalStyle` from styled components). In the case of already being able to use js within your styles, it might be better to store it as a js variable and use string interpolation (`${variable}`) to call it. – Dennis Martinez Nov 24 '19 at 18:11
  • The + is the adjacent sibling selector [link]( https://developer.mozilla.org/en-US/docs/Web/CSS/Adjacent_sibling_combinator ). I believe the problem is that the var(--height) inside that selector is trying to get a variable on the selected element rather than a reference to the --height variable defined above – JensM Nov 24 '19 at 18:13
  • @DennisMartinez how do I change that variable in a media query then? – JensM Nov 24 '19 at 18:14
  • @JensM Do you have an example of that? – Dennis Martinez Nov 24 '19 at 18:14
  • I just found a solution, I'll write an official answer – JensM Nov 24 '19 at 18:23
  • Awesome, glad to hear! If you need some context around my above comment, here's a codesandbox https://codesandbox.io/s/dazzling-mayer-2xmvu. – Dennis Martinez Nov 24 '19 at 18:26
  • @DennisMartinez I just posted it. Please tell me if I did anything wrong! I think this is a good solution because I still wanted to be able to manipulate the height variable using CSS (media queries) instead of JS because that would cause a lot of `React` rerenders. – JensM Nov 24 '19 at 18:38
  • related question: https://stackoverflow.com/q/55931363/8620333 almost the same situtation – Temani Afif Nov 24 '19 at 19:00

1 Answers1

2

As @DennisMartinez said,

"CSS custom properties work when cascading. What this means is that it'll work on the element in which it's declared and that elements descendants. A sibling is not a descendant, thus you're faced with this issue."

This means it is really hard to solve this. I believe the following solution is likely to be the best, as the limitation above makes it hard to think of something better.

I ended up declaring the variable twice, once inside the Header styling itself and once inside the sibling selector. I can interpolate this if I want to so I can have a single source of truth still. I wanted the variable to be changed by a media query, so I declared this one twice too, in the same places as the variable. This copy-paste can be extracted into a string and interpolated if I want to.

Here is the code:

export const S_Header = styled.header<HeaderProps>`
  --height: 20vw;

  position: fixed;
  top: 0;

  width: 100vw;
  height: var(--height);

  @media (${devices.medium}) {
    --height: 10vw;
  }

  @media (${devices.large}) {
    --height: 5vw;
  }

  /* Give the next sibling a top padding so it doesn't start underneath the header */
  + * {
    --height: 20vw;

    padding-top: var(--height);
    margin-top: 0;
    border-top: 0;

    @media (${devices.medium}) {
      --height: 10vw;
    }

    @media (${devices.large}) {
      --height: 5vw;
    }
  }
`;

I also wrote this using the interpolations I mentioned above:

const heightVar = "--height: 20vw;";

const mediaQuery = ` 
  @media (${devices.medium}) {
    --height: 10vw;
  }

  @media (${devices.large}) {
    --height: 5vw;
  }
`;

export const S_Header = styled.header<HeaderProps>`
  ${heightVar}

  position: fixed;
  top: 0;

  width: 100vw;
  height: var(--height);

  z-index: 98;

  transform: ${(props): string =>
    props.visible === false ? "translateY(-100%)" : "none"};

  background-color: ${(props): string =>
    props.atTop ? "transparent" : props.bgColor};

  transition: transform 0.35s, background-color 0.35s ease-in-out;

  ${mediaQuery}

  /* Give the next sibling a top padding so it doesn't start underneath the header */
  + * {
    ${heightVar}

    padding-top: var(--height);
    margin-top: 0;
    border-top: 0;

    ${mediaQuery}
  }
`;
JensM
  • 143
  • 1
  • 11