Pular para o conteúdo principal

Integração por Plataforma

Premissa

O Aplica Tokens Theme Engine gera tokens em múltiplos formatos (dist/css/, dist/esm/, dist/cjs/, dist/json/, dist/dts/). Cada plataforma consome um formato diferente. Este guia mostra o padrão de integração para cada ambiente.


CSS-Only (qualquer stack)

O formato mais simples — carregue os arquivos CSS gerados e use as custom properties diretamente.

Instalação

# Copiar dist/css/ para seu projeto, ou consumir do pacote
# npm install @aplica/tokens (quando publicado como pacote)

Carregamento

<!-- Carregar o tema padrão -->
<link rel="stylesheet" href="/tokens/aplica_joy-light-positive.css">

<!-- Carregar múltiplos temas (todos disponíveis para troca em runtime) -->
<link rel="stylesheet" href="/tokens/aplica_joy-light-positive.css">
<link rel="stylesheet" href="/tokens/aplica_joy-dark-positive.css">

<!-- Definir o tema ativo no elemento raiz -->
<html data-theme="aplica_joy-light-positive">

Troca de tema

// Trocar para dark mode
document.documentElement.setAttribute('data-theme', 'aplica_joy-dark-positive');

// Com preferência do sistema
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.setAttribute(
'data-theme',
`aplica_joy-${prefersDark ? 'dark' : 'light'}-positive`
);

Uso nos estilos

.btn-primary {
background: var(--semantic-color-interface-function-primary-normal-background);
color: var(--semantic-color-interface-function-primary-normal-txt-on);
padding: var(--semantic-dimension-spacing-extra-small) var(--semantic-dimension-spacing-small);
border-radius: var(--semantic-border-radii-small);
}

/* Sem CSS extra — dark mode funciona automaticamente via data-theme */

React / Vite / Next.js

Instalação e carregamento do CSS

// main.tsx ou _app.tsx (Next.js)
// Importar o CSS do tema padrão
import '@aplica/tokens/css/aplica_joy-light-positive.css';
import '@aplica/tokens/css/aplica_joy-dark-positive.css';
// _app.tsx (Next.js) ou main.tsx (Vite)
// Definir o tema no elemento raiz na carga inicial
useEffect(() => {
const saved = localStorage.getItem('theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const theme = saved ?? (prefersDark ? 'aplica_joy-dark-positive' : 'aplica_joy-light-positive');
document.documentElement.setAttribute('data-theme', theme);
}, []);

Componentes com CSS Modules

// Button.module.css
.root {
background: var(--semantic-color-interface-function-primary-normal-background);
color: var(--semantic-color-interface-function-primary-normal-txt-on);
border-radius: var(--semantic-border-radii-small);
padding: var(--semantic-dimension-spacing-extra-small) var(--semantic-dimension-spacing-small);
font-size: var(--semantic-typography-font-sizes-small);
border: 1px solid var(--semantic-color-interface-function-primary-normal-border);
cursor: pointer;
}

.root:hover {
background: var(--semantic-color-interface-function-primary-action-background);
}

.root:active {
background: var(--semantic-color-interface-function-primary-active-background);
}

.root:disabled {
background: var(--semantic-color-interface-function-disabled-normal-background);
color: var(--semantic-color-interface-function-disabled-normal-txt-on);
cursor: not-allowed;
}
// Button.tsx
import styles from './Button.module.css';

interface ButtonProps {
variant?: 'primary' | 'secondary';
children: React.ReactNode;
disabled?: boolean;
onClick?: () => void;
}

export function Button({ variant = 'primary', children, disabled, onClick }: ButtonProps) {
return (
<button
className={styles[variant]}
disabled={disabled}
onClick={onClick}
>
{children}
</button>
);
}

Hook para troca de tema

// hooks/useTheme.ts
import { useState, useEffect, useCallback } from 'react';

type Mode = 'light' | 'dark';
type Surface = 'positive' | 'negative';

interface UseThemeReturn {
mode: Mode;
surface: Surface;
setMode: (mode: Mode) => void;
setSurface: (surface: Surface) => void;
themeKey: string;
}

const BRAND = 'aplica_joy'; // configurar por projeto

export function useTheme(): UseThemeReturn {
const [mode, setModeState] = useState<Mode>('light');
const [surface, setSurfaceState] = useState<Surface>('positive');

const applyTheme = useCallback((m: Mode, s: Surface) => {
const key = `${BRAND}-${m}-${s}`;
document.documentElement.setAttribute('data-theme', key);
localStorage.setItem('theme-mode', m);
localStorage.setItem('theme-surface', s);
}, []);

useEffect(() => {
const savedMode = localStorage.getItem('theme-mode') as Mode | null;
const savedSurface = localStorage.getItem('theme-surface') as Surface | null;
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;

const initialMode = savedMode ?? (prefersDark ? 'dark' : 'light');
const initialSurface = savedSurface ?? 'positive';

setModeState(initialMode);
setSurfaceState(initialSurface);
applyTheme(initialMode, initialSurface);
}, [applyTheme]);

const setMode = useCallback((m: Mode) => {
setModeState(m);
applyTheme(m, surface);
}, [surface, applyTheme]);

const setSurface = useCallback((s: Surface) => {
setSurfaceState(s);
applyTheme(mode, s);
}, [mode, applyTheme]);

return {
mode,
surface,
setMode,
setSurface,
themeKey: `${BRAND}-${mode}-${surface}`,
};
}

Acesso a tokens via ESM (cálculos em JavaScript)

Quando você precisar usar tokens em lógica JavaScript — animações, cálculos de layout, bibliotecas de canvas:

// Importar tokens como módulo ES
import { semantic } from '@aplica/tokens/esm/aplica_joy-light-positive-semantic.mjs';

// Usar em cálculo
const spacing = parseInt(semantic.dimension.spacing.medium); // 32 (em px)
const doubleSpacing = `${spacing * 2}px`; // '64px'

// Passar para uma biblioteca de animação
gsap.to('.element', {
duration: 0.3,
padding: semantic.dimension.spacing.small // '24px'
});

TypeScript — autocompletar com as declarations

// tsconfig.json — incluir as declarações geradas
{
"compilerOptions": {
"paths": {
"@aplica/tokens/*": ["./node_modules/@aplica/tokens/dist/*"]
}
}
}
// Acesso com tipos e autocompletar
import { semantic } from '@aplica/tokens/dts/aplica_joy-light-positive-semantic';

// TypeScript sabe que background é string
const color: string = semantic.color.interface.function.primary.normal.background;

Vue 3 / Nuxt

O padrão é idêntico ao React no que diz respeito a tokens — CSS custom properties funcionam da mesma forma.

Carregamento

// main.ts
import '@aplica/tokens/css/aplica_joy-light-positive.css';
import '@aplica/tokens/css/aplica_joy-dark-positive.css';

Composable para tema

// composables/useTheme.ts
import { ref, computed, onMounted } from 'vue';

const BRAND = 'aplica_joy';

export function useTheme() {
const mode = ref<'light' | 'dark'>('light');
const surface = ref<'positive' | 'negative'>('positive');

const themeKey = computed(() => `${BRAND}-${mode.value}-${surface.value}`);

function applyTheme() {
document.documentElement.setAttribute('data-theme', themeKey.value);
localStorage.setItem('theme-mode', mode.value);
localStorage.setItem('theme-surface', surface.value);
}

function setMode(m: 'light' | 'dark') {
mode.value = m;
applyTheme();
}

function setSurface(s: 'positive' | 'negative') {
surface.value = s;
applyTheme();
}

onMounted(() => {
const savedMode = localStorage.getItem('theme-mode') as 'light' | 'dark' | null;
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
mode.value = savedMode ?? (prefersDark ? 'dark' : 'light');
surface.value = (localStorage.getItem('theme-surface') as 'positive' | 'negative') ?? 'positive';
applyTheme();
});

return { mode, surface, themeKey, setMode, setSurface };
}

Componente Vue com scoped styles

<template>
<button :class="[$style.btn, $style[variant]]" :disabled="disabled">
<slot />
</button>
</template>

<script setup lang="ts">
defineProps<{
variant?: 'primary' | 'secondary';
disabled?: boolean;
}>();
</script>

<style module>
.btn {
border-radius: var(--semantic-border-radii-small);
padding: var(--semantic-dimension-spacing-extra-small) var(--semantic-dimension-spacing-small);
font-size: var(--semantic-typography-font-sizes-small);
cursor: pointer;
}

.primary {
background: var(--semantic-color-interface-function-primary-normal-background);
color: var(--semantic-color-interface-function-primary-normal-txt-on);
}

.primary:hover {
background: var(--semantic-color-interface-function-primary-action-background);
}

.secondary {
background: var(--semantic-color-interface-function-secondary-normal-background);
color: var(--semantic-color-interface-function-secondary-normal-txt-on);
}
</style>

Node.js / Server-Side

Para SSR, scripts de build ou CLI tools que precisam ler valores de tokens:

// CommonJS
const { semantic } = require('@aplica/tokens/cjs/aplica_joy-light-positive-semantic.js');

console.log(semantic.color.interface.function.primary.normal.background); // '#C40145'
console.log(semantic.dimension.spacing.medium); // '32px'
// ESM (Node.js 18+, com "type": "module" no package.json)
import { semantic } from '@aplica/tokens/esm/aplica_joy-light-positive-semantic.mjs';

Geração dinâmica de CSS no servidor

// Gerar CSS com tokens para injeção no HTML (SSR)
const { semantic } = require('@aplica/tokens/cjs/aplica_joy-light-positive-semantic.js');
const { foundation } = require('@aplica/tokens/cjs/aplica_joy-foundation.js');

function generateInlineCSS() {
// O dist/css/ já contém isso pronto — prefira servir o arquivo estático
// Esta abordagem só faz sentido se precisar de transformação dinâmica
return `
:root {
--primary-bg: ${semantic.color.interface.function.primary.normal.background};
--body-text: ${semantic.color.text.body};
}
`;
}

Na maioria dos casos, servir dist/css/ como arquivos estáticos é mais eficiente que gerar CSS no servidor. O build do engine já gerou o CSS otimizado.


Mobile — React Native

React Native não usa CSS custom properties. Os tokens de valor (ESM/CJS em px) são a forma de integração.

// tokens/aplica-joy-light.ts
import { semantic } from '@aplica/tokens/esm/aplica_joy-light-positive-semantic.mjs';

// Converter px strings para números para React Native
function px(value: string): number {
return parseFloat(value); // '32px' → 32
}

export const tokens = {
colors: {
primary: semantic.color.interface.function.primary.normal.background,
primaryText: semantic.color.interface.function.primary.normal.txtOn,
textBody: semantic.color.text.body,
successBg: semantic.color.interface.feedback.success.default.normal.background,
},
spacing: {
xs: px(semantic.dimension.spacing.extraSmall), // 8
sm: px(semantic.dimension.spacing.small), // 12
md: px(semantic.dimension.spacing.medium), // 16
lg: px(semantic.dimension.spacing.large), // 20
xl: px(semantic.dimension.spacing.extraLarge), // 24
},
radius: {
sm: px(semantic.border.radii.small),
md: px(semantic.border.radii.medium),
circular: 9999,
},
typography: {
fontFamily: semantic.typography.fontFamilies.main, // 'Inter'
sizeBody: px(semantic.typography.fontSizes.medium), // 16
sizeSmall: px(semantic.typography.fontSizes.small), // 12
},
};
// Button.tsx (React Native)
import { StyleSheet, TouchableOpacity, Text } from 'react-native';
import { tokens } from '../tokens/aplica-joy-light';

const styles = StyleSheet.create({
button: {
backgroundColor: tokens.colors.primary,
paddingHorizontal: tokens.spacing.md,
paddingVertical: tokens.spacing.xs,
borderRadius: tokens.radius.sm,
},
label: {
color: tokens.colors.primaryText,
fontFamily: tokens.typography.fontFamily,
fontSize: tokens.typography.sizeBody,
},
});

export function Button({ label, onPress }: { label: string; onPress: () => void }) {
return (
<TouchableOpacity style={styles.button} onPress={onPress}>
<Text style={styles.label}>{label}</Text>
</TouchableOpacity>
);
}

Dark mode no React Native: importe o conjunto de tokens do tema dark e troque o objeto de tokens via context quando o modo mudar. Como não há CSS custom properties, a troca de tema requer re-render dos componentes afetados.

// contexts/ThemeContext.tsx (React Native)
import { createContext, useContext, useState } from 'react';
import { lightTokens } from '../tokens/aplica-joy-light';
import { darkTokens } from '../tokens/aplica-joy-dark';

const ThemeContext = createContext(lightTokens);

export function ThemeProvider({ children }) {
const [tokens, setTokens] = useState(lightTokens);

function toggleTheme() {
setTokens(prev => prev === lightTokens ? darkTokens : lightTokens);
}

return (
<ThemeContext.Provider value={{ tokens, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}

export const useTokens = () => useContext(ThemeContext).tokens;

Checklist de Integração

Web (CSS + qualquer framework)

  • Arquivos dist/css/*.css carregados (todos os temas necessários)
  • data-theme definido no elemento raiz na carga
  • Lógica de preferência do sistema (prefers-color-scheme) implementada
  • Preferência do usuário persistida (localStorage)
  • Nenhum hex hardcoded nos estilos de componente
  • Variáveis CSS resolvem corretamente (inspecionar no DevTools)

JavaScript / TypeScript

  • Imports ESM ou CJS apontando para o tema correto
  • dist/dts/*.d.ts incluído no tsconfig (para autocompletar)
  • Valores px convertidos para number antes de passar ao React Native

Build / CI

  • CSS dos tokens incluído no bundle de produção
  • Arquivos de tema carregados antes do primeiro render (sem flash)
  • Tree shaking funcionando (apenas temas usados no bundle final)

Referências