Mixins
A mixin defines when styles apply. It also called Stateful Intents.
If intents are about “What styles to apply?” then mixins are about “Under what conditions to apply those styles?”
Inside a .css file, Shilp CSS scans for mixin syntax. It looks for patterns
that start with @ and end with {.
Why Mixins Exist
CSS is not only about properties.
It is also about:
- States (
hover,focus,disabled) - Themes (
dark,light) - Breakpoints (
md,xl) - Data attributes
- Structural relationships
- and so on...
Mixins exist to keep those conditions, structured and configurable.
Instead of hardcoding selectors everywhere, you define them once in config.
What Is a Mixin?
A mixin simply looks like @<mixin-name> variant {.
In simple words:
- Starts with
@ - Followed by mixin name and whitespace
- Followed by a variant and whitespace
- Ends with
{
Examples:
@state hover { ... }@theme dark { ... }@screen md { ... }@screen md:xl { ... }@data is("state=disabled") { ... }
Parsing
Internally, Shilp CSS looks for RegExp pattern
/@([a-z-]+)\s([a-z-:]+)(\(.+\))?\s{/g.
For mixin @state hover { ... }, it splits as below:
- Mixin name:
"state" - Variant:
"hover" - Content: any styles wrapped inside mixin
That's it. Mixin = Mixin name + variant + wrapped styles
Location
Mixins are defined in your config at shilpConfig.mixins.
Each root-level key is a mixin name and value is a mixin config.
Example:
shilp.config.jsconst shilpConfig = { source: "react", mixins: { state: { ... }, theme: { ... }, }, }; export default shilpConfig;
This creates two mixins, "state" and "theme".
Basic Shape of Mixin Config
Mixin configconst mixinConfig = { disable: boolean, // optional resolve: function, // optional variants: object // optional if `resolve` defined }
variants
Defines available variants and what selector they map to.
Each key is a variant name.
Each value can be:
- A string
- An array
- A function-style placeholder pattern
String Variant
State mixinconst mixinConfig = { variants: { hover: "&:hover"; } }
Usage: @state hover { ... } ➝ &:hover { ... }
Array Variant
State mixinconst mixinConfig = { variants: { "custom-hover": ["@media (hover: hover)", "&:hover"], }, };
Usage: @state custom-hover { ... } ➝
@media (hover: hover) { &:hover { ... } }
Array creates nested wrapping.
Function Variants
Variants can accept arguments.
Placeholders use <1>, <2>, etc for argument replacement order.
RegExp Pattern: /<([^>]+)>/g
Example:
Data mixinconst mixinConfig = { variants: { is: "&[data-<1>]", "custom-is": "&[data-<1>=<2>]", }, };
Usage:
@data is("state=top")➝&[data-state=top] { ... }@data custom-is("state", "top")➝&[data-state=top] { ... }
Note on Arguments
When using function variants, arguments must be wrapped in quotes.
If an argument contains a comma, it will otherwise be split into multiple arguments:
- Incorrect:
@match not(.a, .b) { ... }➝ ❌ - Correct:
@match not(".a, .b") { ... }➝ ✅
As :not() accepts a comma-separated list, wrapping it in quotes ensures it is
treated as a single argument. This way you can pass any number or selectors to
:not().
Otherwise you have to define exact number of arguments in mixin with <1>,
<2>, etc explicitely and pass the exact same amount of arguments, every single
time.
If argumetns number did not match, SCSS compiler will throw error.
resolve
It is custom mixin resolver. You can override default mixin behavior with it.
Custom mixin resolverconst shilpConfig = { source: "react", // internal defaults theme: { ... }, mixins: { ... }, extend: { mixins: { ipsum: { resolve: (options) => { // custom mixin resolver logic return { definition, selector }; }, }, }, } } export default shilpConfig;
Available Options
Custom mixin resolver mixinConfig.resolve receives a single argument
(object) with following options:
| Option | Type | Description |
|---|---|---|
options.content | string | Raw CSS string |
options.filePath | string | Path of raw CSS string |
options.config | object | Full shilp config object |
options.mixinName | string | Mostly for error reporting |
options.variantName | string | Mixin variant name |
options.mixinConfig | object | Full mixins config |
options.fn | string | Arguments if function variant, else empty string |
options.raw | string | Raw mixin definition mostly for error reporting |
Expected Output
In return it expects an object with following options:
definition(string) - SCSS's full@mixindefinition- This is optional
- It must follow
SCSS
@mixinPattern
selector(string) - Final selector to replace Shilp CSS mixin syntax- If
definitionis provided, then it must follow SCSS@includePattern - If
definitionnot provided, then it must be resolved selector (validCSSselector)
- If
disable
You can disable any mixin.
For example, to disable state mixin:
Disable state mixinconst shilpConfig = { source: "react", extend: { mixins: { state: { disable: true, }, }, }, }; export default shilpConfig;
This removes the mixin, and any styles defined inside it (during processing).
Even if @state exists in your CSS, it will not generate output.
This can be useful when:
- Integrating third-party CSS
- Reducing bundle size
- Restricting allowed styling patterns
From Config to CSS
Given this config:
shilp.config.jsconst shilpConfig = { source: "react", properties: { text: { thick: { property: "font-weight: <v><i>;", values: { 500: 500, 600: 600, }, }, }, } mixins: { state: { variants: { hover: ["@media (hover: hover)", "&:hover"], }, }, }, }; export default shilpConfig;
You can write:
.any-class { @text thick-500; @state hover { @text thick-600; } }
And it becomes:
.any-class { font-weight: 500; } @media (hover: hover) { .any-class:hover { font-weight: 600; } }
Mixins determines which styles to apply based on state or condition.
High-Level Mixins Processing
At the beginning of processing:
- Internal default mixins are created and loaded at
shilpConfig.mixins - Root-level mixins
shilpConfig.mixinsoverride them completely (if provided)- This is not recommended. Avoid this.
shilpConfig.extend.mixinsis deep merged withshilpConfig.mixins
This defines which mixins are available.
Mixin processing happens during build generation. There is no runtime evaluation.
When mixin is detected:
- Extract mixin name
- Extract variant
- Extract arguments (if any)
- Traverse
shilpConfig.mixins - Resolve selector
- Generate SCSS
@mixin - Replace usage with
@include
At this stage, mixin processing is done.
Further processing is handled by the
SCSS @mixin.
Reserved Names
Available Built-in Mixins Dataset
All built-in mixins datasets are documented separately.
You can explore the full list in the MIXINS section of the documentation.
Each dataset has its own page: /docs/mixins/<mixin-name>
Examples:
/docs/mixins/state/docs/mixins/data/docs/mixins/theme
Those pages show exact mixins dataset structure with usage example.