# no-dark-without-light

Regla de oxlint que reporta un color `dark:` de Tailwind CSS, como `dark:bg-gray-900`, sin una clase
que defina ese color para el modo claro en el elemento.

## De un vistazo

| Autofix | Sugerencias en el editor | Design system                         | Opciones                 |
| ------- | ------------------------ | ------------------------------------- | ------------------------ |
| No      | No                       | Opcional — se usa si hay `entryPoint` | `variants`, `entryPoint` |

## Qué hace esta regla

Atrapa el caso donde escribiste `dark:bg-gray-900` pero te olvidaste de la base correspondiente en
light mode (`bg-white`, `bg-zinc-50`, lo que sea). En el momento en que un theme switcher está
activo, ese elemento renderiza sin estilo en light mode — típicamente transparente o heredando del
padre, casi nunca lo que querías.

**Solo el color necesita base.** Un filtro, un display, un ancho de borde o una opacidad que solo
aplican en modo oscuro dejan el modo claro con el render normal del elemento — ese es el punto de
`dark:brightness-[0.2]` en una imagen, o de `dark:hidden` en una decoración. Así que solo se revisa
una clase con variante que define un color: con un entry point configurado, "define un color" se lee
del CSS que emite la clase (una propiedad de color como `color`, `background-color` o `fill`, una
variable de theme `--color-*`, o un literal de color); sin él, se revisa toda clase salvo las
familias que nunca son color — filtros y filtros de backdrop, opacidad, display, visibilidad,
position, `sr-only`, y anchos de border / outline / ring. `dark:text-white` sola se sigue
reportando: el color del texto es color.

El chequeo es por grupo, no por valor exacto: por cada clase que usa una variante observada, ¿hay al
menos una clase del mismo grupo que NO usa una? Si no, la clase con variante no tiene contraparte en
light mode y se reporta.

Una clase cuenta como base de dos maneras:

- **Mismo prefijo de utility** — `bg-white` es la base de `dark:bg-black`, `text-gray-900` la de
  `dark:text-white`. Es la familia de propiedad inicial (`bg`, `text`, `border-t`, `from`,
  `rounded-tl`, …).
- **Misma propiedad CSS declarada**, cuando `settings.tailwindcss.entryPoint` (o el `entryPoint`
  propio de la regla) está configurado. `underline` y `no-underline` no comparten prefijo alguno,
  pero las dos escriben `text-decoration-line` — así que `underline dark:no-underline` es un par
  completo, no una base faltante. Lo mismo vale para `italic dark:not-italic`,
  `visible dark:invisible`, `uppercase dark:normal-case`, `truncate dark:text-clip` y
  `sr-only dark:not-sr-only`, que antes se reportaban todos.

El chequeo por propiedad es **aditivo**: todo lo que ya coincidía por prefijo sigue coincidiendo,
así que configurar un entry point solo puede hacer que esta regla reporte menos, nunca más. Sin él
cae al chequeo por prefijo más una tabla interna pequeña para `display` y `position` (para que
`block dark:hidden` funcione), y por eso los pares de arriba siguen reportándose cuando no hay CSS
configurado.

El conjunto de "variantes observadas" es configurable. Por defecto es sólo `dark`, pero el mismo
shape aplica a cualquier otra variante estilo scheme (`contrast-more`, `motion-reduce`, `print`,
variantes personalizadas de data-attribute que tengas registradas, etc.) — mira la opción `variants`
abajo.

## Opciones

### `variants`

`string[]`, default `["dark"]`.

Lista de variantes que requieren una base sin variante en el mismo prefijo de utility.
Sobreescríbelo cuando tu app usa otro mecanismo para cambiar de theme (e.g. `contrast-more:` para un
tema de alto contraste, o una variante personalizada `theme-foo:` definida en tu CSS).

```jsonc
{ "tailwindcss/no-dark-without-light": ["error", { "variants": ["dark", "contrast-more"] }] }
```

Si estableces `variants` a un array vacío la regla se convierte en un no-op — prefiere desactivarla
directamente en ese caso.

### `entryPoint`

`string`, opcional. Un entry point CSS solo para esta regla, que pisa
`settings.tailwindcss.entryPoint`. Habilita el agrupamiento por propiedad declarada descrito arriba;
la regla funciona sin él.

## Ejemplos

### ✗ Incorrecto

```tsx
// Sin fondo en light mode — el elemento queda desnudo cuando dark no aplica
<div className="dark:bg-gray-900" />

// `bg-*` tiene base, pero `text-*` no
<div className="bg-white dark:bg-gray-900 dark:text-white" />
```

### ✓ Correcto

```tsx
// Ambos modos cubiertos
<div className="bg-white text-black dark:bg-gray-900 dark:text-white" />

// Misma propiedad con otro nombre — hace falta un entryPoint para reconocerlo
<div className="underline dark:no-underline" />
<div className="italic dark:not-italic" />
<div className="visible dark:invisible" />

// Mostrar en light, ocultar en dark: las dos escriben `display`
<div className="block dark:hidden" />

// No es un color: el modo claro mantiene el render normal del elemento a propósito
<img className="object-cover dark:brightness-[0.2] dark:grayscale" />
<div className="flex-col dark:border-r" />

// No hay variante observada → la regla no se mete con la cobertura de base
<div className="hover:bg-blue-500" />

// Variante personalizada + base correspondiente
// options: { "variants": ["contrast-more"] }
<div className="bg-white contrast-more:bg-black" />

// Un string sólo-dark dentro de un helper de merge o en un componente personalizado es
// un fragmento de override — la base light vive en otro lado, así que NO se
// reporta. Mira "Composición y helpers de merge" abajo.
cn("dark:bg-gray-900")
;<Field className="dark:bg-transparent" />
```

## Interacciones con otras reglas

- **`no-contradicting-variants`**: forma inversa. Esta regla marca `dark:foo` sin base; esa marca
  `foo dark:foo` donde la base hace redundante a la variante. Nunca se contradicen sobre el mismo
  par de clases.
- **`no-conflicting-classes`**: ortogonal. Las clases en conflicto chocan en una propiedad; "missing
  base" es la ausencia de una propiedad para un modo determinado. Un elemento sólo-dark no va a
  romper `no-conflicting-classes` porque no entra en conflicto — simplemente no renderiza como
  querías.
- **`enforce-sort-order`**: no afecta la detección (la regla no mira el orden), pero al ordenar la
  base y la variante dark tienden a quedar pegadas, lo que hace evidente la que falta en review.

## Composición y helpers de merge (`cn` / `twMerge` / `cva`)

"No hay base para esta clase `dark:`" sólo se sostiene cuando el string que se revisa es la lista de
clases **final y autocontenida** del elemento. En una composición con `tailwind-merge` — el patrón
`cn`/`twMerge` + `cva` que usan los codebases estilo shadcn — un `className` suele ser un
**fragmento de override** que se fusiona en runtime con clases aportadas en otro lado, muchas veces
en otro módulo. Un override sólo-dark como `<Field className="dark:bg-transparent" />` no le falta
nada: la base light (`bg-white`) vive en el `cva` propio del componente, y `tailwind-merge` conserva
ambas porque están en scopes de modificador distintos. Reportarlo — y "arreglarlo" agregando una
base light al override — cambia en silencio el render en light mode (issue #117).

Como esa base vive en otro argumento o módulo, la redundancia no es decidible desde el fragmento
solo sin emular `tailwind-merge`. Por eso la regla sólo evalúa un string que puede asegurar que es
la lista final: un literal (o template) usado directamente como `class`/`className` en un **elemento
host nativo** (`<div>`, `<input>`, …). **No** reporta strings que sean:

- argumentos de una llamada merge-aware — `cn(...)`, `twMerge(...)`, `cva(...)`, `tv(...)`, etc.;
- el `className`/`class` de un **componente personalizado** (`<Field …>`, `<Card.Body …>`), que es
  opaco — el componente puede volver a fusionarlo internamente;
- asignados a una variable, o emitidos desde un tagged template.

El trade-off es deliberado: acepta algunos falsos negativos (una clase genuinamente sólo-dark
escondida en una de esas posiciones no se atrapa) para eliminar los falsos positivos, que se leen
como obviamente correctos e invitan a una regresión de render.

## Cuándo desactivarla

- **Apps con un único color scheme** que igual incluyen algunas clases `dark:` para overrides
  puntuales sobre una base ya correcta en un componente padre. Prefiere angostar `variants` (e.g.
  sacar `dark`) antes que desactivarla.
- **Strings de clases servidas desde el server** en un elemento nativo donde la base vive en CSS y
  sólo el override dark se emite desde JS. (Los fragmentos de override que pasan por `cn`/`twMerge`
  o por un componente personalizado ya se omiten — mira la sección de arriba.)
