Composing class names
A class name targeting styles of the sub-components created by composition.
Defining nested className
value
To define a nested class names for the sub-components use a nested object.
The only exception being the HOST_KEY
.
import { defineClassName } from "@site/../packages/class-name/dist";
const subComponentsClassName = defineClassName({
// 2.1: Stateless:
header: 'hello world',
// 2.2: Stateful:
content: ({ active }: { active: boolean }, previous) => [...previous, 'lorem', 'ipsum', 'dolor', 'sit', active ? 'amet' : null],
footer: ({ active }: { active: boolean }, previous) => [...previous, `footer-is-active:${active}`],
})
Reference
Merging any two class names
To combine 2 class names use mergeClassNames()
.
It transforms arguments into objects first and than deeply appends non-nested class name values to a special HOST_KEY
property while keeping the structure.
import { mergeClassNames } from "@site/../packages/class-name/dist";
const nestedClassName = mergeClassNames(
// 1. Host class name:
({ active }: { active: boolean }, previous) => [...previous, `active:${active}`],
// 2. Sub-component class names:
{
// 2.1: Stateless:
header: 'hello world',
// 2.2: Stateful:
content: ({ active }: { active: boolean }, previous) => [...previous, 'lorem', 'ipsum', 'dolor', 'sit', active ? 'amet' : null],
footer: ({ active }: { active: boolean }, previous) => [...previous, `footer-is-active:${active}`],
},
)
container
or &
for the host class name property?You could use any property than HOST_KEY
. It you would require you to merge other types of values coming through className
(e.g. string
) prop
with the values coming via nested container
property together yourself.
Full example
// 1. Define imports
import type { ClassNameProp } from '@unwind/class-name'
import { defineClassName, mergeClassNames, resolveClassName } from '@unwind/class-name'
import { memo, PropsWithChildren } from 'react'
// 2. Define class name state:
type MyComponentClassNameState = { active: boolean }
// 3. Define host (container) class name:
const myComponentHostClassName = defineClassName(({ active }: MyComponentClassNameState, previous) => [
...previous,
'my-component',
active ? 'my-component-is-active' : null,
])
// 4. Define class name for sub-components and merge with the host class name:
const myComponentClassName = mergeClassNames(myComponentHostClassName, {
icon: ({ active }: MyComponentClassNameState, previous) => [
...previous,
'my-component-icon',
active ? 'my-component-icon-is-active' : null,
],
content: ({ active }: MyComponentClassNameState, previous) => [
...previous,
'my-component-content',
active ? 'my-component-content-is-active' : null,
],
})
type MyComponentProps = PropsWithChildren<{
active?: boolean,
// 5. Define the prop:
className?: ClassNameProp<typeof myComponentClassName>
}>
const MyComponent = memo<MyComponentProps>(({ active = false, children, className }) => {
// 6. Extend your styles with the outer class name:
const styles = mergeClassNames(myComponentClassName, className)
return (
// 7. Resolve string representations:
<div className={resolveClassName({ active }, styles)}>
<span className={resolveClassName({ active }, styles.icon)} />
<div className={resolveClassName({ active }, styles.content)}>
{children}
</div>
</div>
)
})
MyComponent.displayName = 'MyComponent'
HOST_KEY
A HOST_KEY
object property is used to hold host class selector values
and the other alpha character keys can be used for sub-components.
Representing className
in Javascript
Mapping own host element value and values of it's children onto simple object is not 1:1.
Using $
is a simple compromise to be able to represent nested class name bearing both class name of the host and sub-components.
In markup languages like XML, HTML, or JSX nodes can have attribute values alongside the children nodes.
Options to mapping nested component tree onto object and vice versa:
1. Fails on either the host element or the children class names
[ hostClassName ]
↔
<Button className={hostClassName}>
<Icon className={iconClassName} />
<Text className={textClassName} />
</Button>
↔
↔
{
icon: iconClassName,
text: textClassName,
}
2. Works with $
for the host element as well as for the children class names:
{
$: [ hostClassName ],
icon: iconClassName,
text: textClassName,
}
↔
↔
↔
<Button className={hostClassName}>
<Icon className={iconClassName} />
<Text className={textClassName} />
</Button>
Why $
?
HOST_KEY
directly, use import:import HOST_KEY from '@unwind/class-name'
Although Javascript is not limited to ASCII set, a $
character:
- can be used without quotes in object property identifier (as well as alphanumeric, or
_
) - can be the first character of in an identifier.
- is still quite easy to write (when needed)
- is more visible than
_
Using a $
instead of a '&'
in SASS or Less to denote host (parent) element is just a matter of ergonomics due &
would require quotes whan writing or accessing the value.
// Creating:
const nested = {
$: ...,
...,
}
// Accessing:
nested.$
// Creating:
const nested = {
'&': ...,
...,
}
// Accessing:
nested['&']
Appendix
→ See also JavaScript Reference → Lexical_grammar → Identifiers.