#1893·stylex

@stylexjs/eslint-plugin More options for propLimits

Author: ysulymaCreated Sep 17, 2026Updated Sep 17, 2026
Labelsenhancement

Describe the feature request

It would be helpful to have more options for propLimits, in order to disallow certain literal values and instead require using design tokens defined with stylex.defineVars() or stylex.defineConsts(). Here is a strawman proposal.

Example

typescript
// eslint.config.js
export default [
  {
    ignores: ["dist/**"],
  },
  {
    files: ["src/**/*.{ts,tsx}"],
    plugins: {
      "@stylexjs": stylexPlugin,
    },
    rules: {
      "@stylexjs/valid-styles": [
        "error",
        {
          propLimits: {
            backgroundColor: {
              limit: {
                variable: true,
              },
              reason: "must pass a StyleX variable",
            },
            borderColor: {
              limit: {
                variable: {
                  name: ["colors", "scales"],
                },
              },
              reason:
                "must pass one of the StyleX variables `colors` or `scales`",
            },
            color: {
              limit: {
                variable: {
                  from: "./src/lib/tokens.stylex.ts",
                  name: ["colors", "scales"],
                },
              },
              reason:
                "must pass one of the StyleX variables `colors` or `scales`",
            },
            marginLeft: {
              // array for unions of acceptable values, similar to existing behavior
              limit: [
                "auto",
                {
                  variable: true,
                },
              ],
              reason:
                "must pass a StyleX variable, or the literal string 'auto'",
            },
            outlineColor: {
              limit: [
                { null: true },
                {
                  variable: {
                    from: "./src/lib/tokens.stylex.ts",
                    name: ["colors"],
                    property: ["destructive", "success"],
                  },
                },
              ],
              reason:
                "must pass one of the StyleX variables `colors` or `scales`",
            },
            paddingLeft: {
              limit: {
                cssVar: true,
              },
              reason: "must pass a CSS variable",
            },
            paddingRight: {
              limit: {
                cssVar: ["--padding-sm", "--padding-lg"],
              },
              reason:
                "must pass one of the CSS variables `--padding-sm` or `--padding-lg`",
            },
            zIndex: {
              limit: [
                {
                  const: {
                    from: "./src/lib/tokens.stylex.ts",

                    // name can be a string if there is only one
                    name: "layers",
                  },
                },
                {
                  calc: true,
                },
              ],
              reason: "must pass a `layers.*` constant from #/lib/tokens.stylex.ts, or a calc() expression",
            },
          },
        },
      ],
    },
  },
];

// component.tsx
import * as stylex from "@stylexjs/stylex";

import { colors, scales as goodScales, spacing } from "#/lib/tokens.stylex.ts";
import { scales as evilScales } from "#/lib/tokens-evil.stylex.ts";

const styles = stylex.create({
  invalid: {
    // invalid: must use a StyleX variable
    backgroundColor: "red",

    // invalid: must use a StyleX variable from the `colors` or `scales` object
    borderColor: spacing.medium,

    // invalid: must use a StyleX variable from the `colors` or `scales` object, and only from `lib/tokens/stylex.ts`
    color: evilScales.blue500,

    // invalid: must use a StyleX variable, or the literal string 'auto'
    marginLeft: "1em",

    // invalid: can only use `colors.destructive` or `colors.success` for outlineColor
    outlineColor: colors.blue,

    // invalid: must pass a CSS variable
    paddingLeft: spacing.medium,

    // invalid: must pass one of the CSS variables `--padding-sm` or `--padding-lg`
    paddingRight: "var(--padding)",

    // invalid: must pass a StyleX `layers.*` constant or a calc() expression
    zIndex: 1,
  },

  valid: {
    // semantically wrong but valid according to above configuration
    backgroundColor: spacing.md,

    borderColor: colors.blue500,

    // validation is against the original export name, not the import name
    color: goodScales.blue500,

    // literal value "auto" explicitly allowed
    marginLeft: "auto",

    outlineColor: {
      ":focus": colors.success,

      // this is why we needed {null: true} in the config
      default: null,
    },
    paddingLeft: "var(--padding)",
    paddingRight: "var(--padding-sm)",
    zIndex: "calc(var(--dialog-level) * 10)",
  },
});

Specification

Variables

{ variable: true } means a variable defined with stylex.defineVars() must be used.

Passing an object allows further constraining the variable:

  • variable.name allows constraining the exported name of the variable. It can be a single string, or an array of strings.
    Potentially could allow globs like pink* to include pink100 through pink900, but this is problematic because a perverse person could use ["pink*"] as a JavaScript key.

  • variable.property allows constraining which property on the variable is used

  • variable.from allows constraining the file the variable is imported from. This is specified by the path relative to the ESLint config file; it is validated after resolving subpath imports and import aliases

Constants

{const: true} and {const: { name: "colors" }} behaves the same as variables, but for stylex.defineConsts() instead of stylex.defineVars().

CSS variables

{ cssVar: true } allows var() expressions. Passing an object allows constraining the name of the variable.

CSS calculations

{calc: true} allows calc() expressions. In this strawman proposal it does not accept further customization.

null

{ null: true } allows null as a value (e.g. for default case in nested blocks)

Remarks

This proposal is really several individual features; for my current use case I'd be quite happy with just being able to do [{null: true}, {variable: true}].

In the current implementation, limit: null is equivalent to limit: {variable: true} since variable values are not validated at all; however, this is causing problems with default: null in nested configs. I am getting around this by doing

typescript
export const nullHack = stylex.defineVars({
  null: null,
});

which is awkward.