v3.0.0-beta.17
Primeros Pasos

Migración

Guía para migrar de Spartan 2.x (TailwindCSS 3) a Spartan 3.x (TailwindCSS 4).

Esta guía cubre la migración de Spartan 2.x (TailwindCSS 3) a Spartan 3.x (TailwindCSS 4). La migración implica pasar del sistema de plugins en JavaScript a una configuración íntegramente basada en CSS.

Prerequisitos

  • Node.js 20.19 o superior
  • NPM ^10
  • Un proyecto existente que use Spartan 2.x

Pasos de migración

Remover el plugin de Spartan

En tu tailwind.config.js, elimina la ruta de contenido de Spartan y su plugin:

tailwind.config.js
module.exports = {
  content: [
    './src/**/*.{vue,js,ts,jsx,tsx}',
-   'node_modules/@placetopay/spartan-vue/dist/**/*.js',
  ],
  plugins: [
-   require('@placetopay/spartan-vue/plugin'),
  ],
}

Ejecutar la herramienta de actualización de TailwindCSS

Usa la herramienta oficial de migración de TailwindCSS para actualizar tu proyecto automáticamente:

npx @tailwindcss/upgrade

Esta herramienta se encarga automáticamente de:

  • Migrar tu tailwind.config.js a configuración basada en CSS
  • Actualizar las clases de utilidades que cambiaron
  • Identificar incompatibilidades
Revisa la guía oficial de migración de TailwindCSS v4 para información detallada sobre los breaking changes.

Remover la configuración de PostCSS (si la usas)

Si estabas usando PostCSS con TailwindCSS 3, elimina el archivo de configuración y las dependencias:

Terminal
rm postcss.config.js
package.json
{
  "devDependencies": {
-   "@tailwindcss/postcss": "^4.1.18",
-   "postcss": "^8.4.33",
  }
}

Instalar el plugin de Vite de TailwindCSS (si usas Vite)

Si tu proyecto usa Vite, instala el plugin oficial de TailwindCSS para Vite:

npm install -D @tailwindcss/vite

Y agrégalo a tu configuración de Vite:

vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  plugins: [
    vue(),
    tailwindcss(),
  ],
})

Actualizar Spartan a 3.x

Instala la última versión de Spartan:

npm install @placetopay/spartan-vue@latest

Actualizar los imports de estilos

Hay dos cambios en cómo se importan los estilos. Primero, remove el import de CSS que tenías en tu entry point:

main.ts
- import '@placetopay/spartan-vue/style.css'
  import { createApp } from 'vue'
  import App from './App.vue'

Luego, agrega el import de Spartan en tu archivo de estilos CSS principal:

styles.css
@import "@placetopay/spartan-vue/styles.css";

@source '../src/**/*.{vue,js,ts,jsx,tsx}';

E importa ese archivo de estilos desde tu entry point:

main.ts
import './styles.css'

import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

Actualizar imports de colores de Tailwind (si los usas)

TailwindCSS 4 cambió la forma de exportar colores. Si importabas colores de Tailwind en archivos JS/TS:

- // Tailwind 3 (ya no funciona)
- import { red, blue } from 'tailwindcss/colors'

+ // Tailwind 4
+ import colors from 'tailwindcss/colors'
+
+ const red = colors.red
+ const blue = colors.blue

Verificar y testear

Realiza un build y levanta el servidor de desarrollo para validar que todo funciona:

Terminal
npm run build
npm run dev

Revisa visualmente tu aplicación para asegurarte de que todos los estilos se aplican correctamente.

Breaking changes

TailwindCSS 4

  • Configuración: Ahora se hace desde CSS con @theme, @plugin, entre otros, en lugar de tailwind.config.js
  • Dark mode: Se configura mediante @custom-variant en CSS
  • Fuentes de contenido: Se usa la directiva @source en CSS en lugar del array content en el archivo de configuración
  • Utilidad ring: ring-3 ya no es válido, usar ring con ancho personalizado si es necesario

Spartan 3.x

  • Sin plugin de JavaScript: Toda la configuración se hace desde CSS
  • Imports simplificados: Un solo @import de styles.css incluye todo
  • Peer dependencies actualizados: Requiere TailwindCSS 4.x
  • SCombobox eliminado: SCombobox y SComboboxBlock (junto con SComboboxOption y SComboboxOptionGroup) fueron eliminados. Usa SSelector y SSelectorBlock como reemplazo — cubren los mismos casos de uso de selección de un valor con búsqueda opcional, con una API mejorada y consistencia visual con el resto de la librería.
  • Variantes de SButton separadas: outline y link pasaron de ser valores de variant a modificadores booleanos. Ver SButton.
  • SFilter reescrito: fields pasa a filters + v-model, y todos los tipos exportados se renombraron. Ver SFilter.
  • SCard.icon reducido: 'primary' y 'secondary' ya no son presets de icon. Ver SCard.
  • Slot de SSelectorBlock renombrado: optionsoption. Ver SSelectorBlock.
  • SModal ya no emite close: se disparaba junto a update:open en el mismo gatillo (clic en el backdrop) sin aportar nada, y chocaba con SModalLeft/SModalSide, donde close significa el botón X. Escucha @update:open en su lugar: <SModal @close="..."><SModal @update:open="open = $event">.
  • Subpath plugin eliminado: @placetopay/spartan-vue/plugin ya no existe; en 3.x no hay plugin JavaScript de Tailwind.
- <SCombobox v-model="value" :display-button-text="getLabel">
-   <SComboboxOption :value="1">Opción 1</SComboboxOption>
-   <SComboboxOption :value="2">Opción 2</SComboboxOption>
- </SCombobox>
+ <SSelector v-model="value" :options="options" />

SButton

Motivación

La API anterior de SButton usaba un enum plano de variant (primary | secondary | danger | outline | link) que mezclaba la intención de color con modificadores de presentación. Esto hacía imposible crear combinaciones como un botón danger outline o un botón secondary link — cada combinación requería su propio valor de variante, generando una API rígida que no podía representar todos los estados definidos en el sistema de diseño de Figma.

Ventajas

  • Modificadores composables: variant define la paleta de color (primary, secondary, danger), mientras que outline, link y circular son modificadores booleanos independientes que se pueden combinar libremente
  • 9+ combinaciones nuevas: variant="danger" outline, variant="secondary" link, variant="danger" link y más — todas previamente imposibles
  • Alineación con Figma: El nuevo modelo coincide directamente con la estructura del sistema de diseño, eliminando la brecha entre diseño e implementación
  • Nuevo modo circular: Botones de solo icono con forma perfectamente redonda, ideales para barras de herramientas y UIs compactas

Migración

outline y link ya no son valores de la prop variant. Ahora son props booleanas independientes:

- <SButton variant="outline">Guardar</SButton>
+ <SButton outline>Guardar</SButton>

- <SButton variant="link">Cancelar</SButton>
+ <SButton link>Cancelar</SButton>

Ambos modificadores usan por defecto la variante primary, preservando el comportamiento visual anterior sin cambios adicionales.

Nuevas combinaciones disponibles:

<!-- Danger outline (antes imposible) -->
<SButton variant="danger" outline>Eliminar</SButton>

<!-- Secondary link / transparente (antes imposible) -->
<SButton variant="secondary" link>Cancelar</SButton>

<!-- Botón circular con icono (nuevo) -->
<SButton circular :icon="AddIcon" />

SFilter

Motivación

SFilter recibía un arreglo fields y lo mutaba en el sitio para guardar el estado de los filtros activos. El arreglo del consumidor era a la vez la fuente de verdad y el borrador interno del componente, así que no había forma de leer los filtros actuales de forma reactiva, reiniciarlos o hidratarlos desde una URL sin meter mano en el mismo arreglo que el componente estaba escribiendo. Las capacidades de cada campo se describían con un objeto interfaces sin tipar, lo que permitía asociar cualquier operador a cualquier campo.

Ventajas

  • v-model: el estado es un Record<string, { operator, value }> plano y serializable, propiedad del consumidor. Nada se muta a tus espaldas.
  • Campos tipados: cada campo declara un type, y sus operators quedan restringidos a los válidos para ese tipo.
  • Definición estática: filters describe qué se puede filtrar; modelValue guarda qué está filtrado ahora. Dejan de estar entrelazados.

Migración

El arreglo fields se convierte en un registro filters indexado por el id del campo, y el estado se mueve a v-model:

- <SFilter :fields="fields" @apply="onApply" />
+ <SFilter v-model="value" :filters="filters" @apply="onApply" />
- const fields = ref([
-     {
-         id: 'name',
-         name: 'Nombre',
-         interfaces: { oneInput: { type: 'text' } },
-         state: { operator: { id: 'contains' }, value: 'ana' },
-     },
- ]);
+ const filters = {
+     name: { type: 'text', label: 'Nombre' },
+ };
+ const value = ref({
+     name: { operator: 'contains', value: 'ana' },
+ });

Cambios en la forma del campo:

Antes (TField)Ahora (SFilterField)
idla clave del registro
namelabel
interfacestype ('text' | 'number' | 'amount' | 'date' | 'dateRange' | 'options' | 'selection')
statela entrada correspondiente en modelValue
validate(value, operator: TOperator)validate(value, operator: string)
permanentpermanent (sin cambios)

Los payloads de los eventos también cambiaron:

- @apply="(fields: TField[]) => ..."
+ @apply="(value: SFilterValue) => ..."   // Record<string, { operator, value }>

- @clear="(fields: TField[]) => ..."
+ @clear="(value: SFilterValue) => ..."

- @save="(data: TSaveData[]) => ..."      // { name, filters }[]
+ @save="(name: string, snapshot: SFilterValue) => ..."

- @load="(filters: TSaveData['filters']) => ..."
+ @load="(snapshot: SFilterValue) => ..."

Se añade un evento delete que emite el nombre del filtro guardado que se elimina, y saved ahora recibe SFilterSaved[] ({ name, snapshot }) en lugar de { name, filters }.

Todos los tipos T* que exportaba SFilter fueron reemplazados por la familia SFilter* (SFilterField, SFilterValue, SFilterEntry, SFilterSaved, SFilterProps, SFilterEmits, …). La tabla de operadores se exporta como valor mediante OPERATORS_BY_TYPE.

Declarar dos operadores con el mismo id en un campo ahora lanza un error al montar. Antes se renderizaba una lista de operadores rota, en silencio.

SCard

Los presets de texto de la prop icon se redujeron de seis a cuatro. 'primary' y 'secondary' ya no son valores válidos de icon — sus glifos por defecto no tenían significado semántico. Siguen disponibles como valores de iconColor.

- <SCard icon="primary" />
+ <SCard icon="info" iconColor="primary" />

Los cuatro presets de estado ('success', 'danger', 'warning', 'info') no cambian.

SSelectorBlock

El slot para personalizar las opciones del dropdown se renombró de options (plural, sin scope) a option (singular, con scope), igualando a SSelector. El slot anterior nunca recibía la opción iterada, así que era imposible renderizar contenido por ítem a través de él.

- <template #options>
-   <span>...</span>
- </template>
+ <template #option="{ option }">
+   <span>{{ option.label }}</span>
+ </template>

Color primario personalizado

Si en Spartan 2.x personalizabas el color primario mediante el plugin, necesitas migrar esa configuración a variables CSS:

- // tailwind.config.js (Spartan 2.x)
- plugins: [
-   require('@placetopay/spartan-vue/plugin')({
-     primary: {
-       50: '228 242 253',
-       500: '33 150 243',
-       // ...
-     }
-   })
- ]

+ /* styles.css (Spartan 3.x) */
+ @import "@placetopay/spartan-vue/styles.css";
+
+ @source '../src/**/*.{vue,js,ts,jsx,tsx}';
+
+ @theme {
+   --color-spartan-primary-50:  rgb(228 242 253);
+   --color-spartan-primary-100: rgb(187 222 251);
+   --color-spartan-primary-200: rgb(144 202 249);
+   --color-spartan-primary-300: rgb(100 181 246);
+   --color-spartan-primary-400: rgb(66 165 245);
+   --color-spartan-primary-500: rgb(33 150 243);
+   --color-spartan-primary-600: rgb(30 132 229);
+   --color-spartan-primary-700: rgb(25 103 196);
+   --color-spartan-primary-800: rgb(21 78 148);
+   --color-spartan-primary-900: rgb(13 42 84);
+ }
Copyright © 2026