How to override styles from a React Child component in a Parent component with Linaria?
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?
There are two reasons why this doesn't work:
- linaria works at build-time and cannot interpolate runtime styles. In React, component properties are unknown until runtime.
- linaria does not support "spreading" styles of one collection into another. This is because each call to
cssfrom the@linaria/corepackage 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:
cxhas 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.
Did this solve your issue? @meesfrenkelfrank