properties
changelog

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:

  1. Mixin name: "state"
  2. Variant: "hover"
  3. 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.js
const shilpConfig = {
	source: "react",

	mixins: {
		state: { ... },
		theme: { ... },
	},
};

export default shilpConfig;

This creates two mixins, "state" and "theme".

Basic Shape of Mixin Config

Mixin config
const 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 mixin
const mixinConfig = {
	variants: {
		hover: "&:hover";
	}
}

Usage: @state hover { ... }&:hover { ... }

Array Variant

State mixin
const 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 mixin
const 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 resolver
const 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:

OptionTypeDescription
options.contentstringRaw CSS string
options.filePathstringPath of raw CSS string
options.configobjectFull shilp config object
options.mixinNamestringMostly for error reporting
options.variantNamestringMixin variant name
options.mixinConfigobjectFull mixins config
options.fnstringArguments if function variant, else empty string
options.rawstringRaw mixin definition mostly for error reporting

Expected Output

In return it expects an object with following options:

  • definition (string) - SCSS's full @mixin definition
  • selector (string) - Final selector to replace Shilp CSS mixin syntax
    • If definition is provided, then it must follow SCSS @include Pattern
    • If definition not provided, then it must be resolved selector (valid CSS selector)

disable

You can disable any mixin.

For example, to disable state mixin:

Disable state mixin
const 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.js
const 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.mixins override them completely (if provided)
    • This is not recommended. Avoid this.
  • shilpConfig.extend.mixins is deep merged with shilpConfig.mixins

This defines which mixins are available.

Mixin processing happens during build generation. There is no runtime evaluation.

When mixin is detected:

  1. Extract mixin name
  2. Extract variant
  3. Extract arguments (if any)
  4. Traverse shilpConfig.mixins
  5. Resolve selector
  6. Generate SCSS @mixin
  7. 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.