linaria icon indicating copy to clipboard operation
linaria copied to clipboard

How to override styles from a React Child component in a Parent component with Linaria?

Open meesfrenkelfrank opened this issue 3 years ago • 2 comments

Environment

"react": "^17.0.2", "react-dom": "^17.0.2", "react-router-dom": "^6.2.2", "react-scripts": "5.0.0", "typescript": "^4.5.5",

"craco": "0.0.3", "craco-linaria": "^1.1.2", "linaria": "^2.3.1"

Description

I need to override css styles in my React child component. I tried different approaches but non of them work?

Approach one: // Child:

import { css, CSSProperties } from "linaria";
import { Link } from "react-router-dom";
import { Color } from "../types/color";

type ClassKey = "root";

export type Classes = Partial<Record<ClassKey, CSSProperties>>;

type Props = {
  title: string;
  url: string;
  classes?: Classes;
} & Color;

const ContactButton = ({ title, url, color, classes }: Props) => {
  const styles = css`
    width: 200px;
    padding: 20px;
    border-radius: 10px;
    border: 2px solid #243447;
    margin-bottom: 40px;
    background-color: var(--link-color);
   ${classes?.root}
  `;

  const handleColor = ({ color }: Color) => {
    switch (color) {
      case "default":
        return "#243447";
      case "red":
        return "#f44336";
      case "green":
        return "#20d830";
      case "blue":
        return "#1d95e6";
      default:
        break;
    }
  };

  return (
    <div
      className={styles}
      style={{ "--link-color": handleColor({ color }) } as CSSProperties}
    >
      <Link to={url}>{title}</Link>
    </div>
  );
};

export default ContactButton;

Then in the parent component:

// Parent:

  const override = css`
    background-color: hotpink;
  `;

        <ContactButton
            classes={{ root: { override } }}
            title="Click me"
            url="/contact"
            color="red"
          />

Approach two: // Child:

import { css, CSSProperties } from "linaria";
import { Link } from "react-router-dom";
import { Color } from "../types/color";

type ClassKey = "root";

export type Classes = Partial<Record<ClassKey, CSSProperties>>;

type Props = {
  title: string;
  url: string;
  style?: CSSProperties;
} & Color;

const ContactButton = ({ title, url, color, style }: Props) => {
  const styles = css`
    width: 200px;
    padding: 20px;
    border-radius: 10px;
    border: 2px solid #243447;
    margin-bottom: 40px;
    background-color: var(--link-color);
    ${style!};
  `;

  const handleColor = ({ color }: Color) => {
    switch (color) {
      case "default":
        return "#243447";
      case "red":
        return "#f44336";
      case "green":
        return "#20d830";
      case "blue":
        return "#1d95e6";
      default:
        break;
    }
  };

  return (
    <div
      className={styles}
      style={{ "--link-color": handleColor({ color }) } as CSSProperties}
    >
      <Link to={url}>{title}</Link>
    </div>
  );
};

export default ContactButton;

Then in the parent component:

// Parent:

  const override = css`
    background-color: hotpink;
  `;

        <ContactButton
            style={{ override }}
            title="Click me"
            url="/contact"
            color="red"
          />

How do I override styles from a React Child component in a Parent component with Linaria?

meesfrenkelfrank avatar Mar 07 '22 08:03 meesfrenkelfrank

There are two reasons why this doesn't work:

  1. linaria works at build-time and cannot interpolate runtime styles. In React, component properties are unknown until runtime.
  2. linaria does not support "spreading" styles of one collection into another. This is because each call to css from the @linaria/core package is replaced with a single generated classname.

The solution is to import css from the @linaria/atomic package. Each call to that is replaced with multiple generated classnames concatenated - one for each CSS-property. The cx function can then be used to merge/override atomic classnames. Example:

import { css, cx } from "@linaria/atomic";

const Child = (props) => {
  const atomicClassNames = css`...`;
  return <div className={cx(atomicClassNames, props.className)} />
};

const Parent () => {
  const atomicClassNames = css`...`;
  return <Child className={atomicClassNames} />
};

From the docs:

cx has the ability to look at the classes it is provided with, and filter duplicates. [based on the CSS-property]

Note: this functionality became available just a few weeks after the issue was opened.

espretto avatar Apr 03 '25 09:04 espretto

Did this solve your issue? @meesfrenkelfrank

espretto avatar Nov 19 '25 16:11 espretto