Primeiros passos
O Medway UI tem dois pacotes de componentes: @medway-ui/core para a web e @medway-ui/native para os apps em React Native. Os dois usam os mesmos tokens e os ícones de @medway-ui/icons.
Encontre o seu cenário na tabela e siga a seção indicada:
| Cenário | Siga |
|---|---|
| Web com Next.js (App Router) | Web, com as notas de Next.js |
| Web com Vite | Web, com as notas de Vite |
| Web com Create React App (craco) | Web, com as notas de CRA |
| Web que já tem CSS próprio (MUI, styled-components, Tailwind com outro tema) | Web e depois Projetos com estilos legados |
| Projeto que seguiu a versão anterior deste guia | Vindo da versão anterior deste guia |
| App Expo que já tem UI própria (Restyle, styled-components) | React Native, modo app existente |
| App Expo novo, todo em NativeWind | React Native, modo app novo |
Web
Requisitos:
@medway-ui/core1.35.1 ou superior.- React 18 ou superior. O pacote ainda aceita o React 17.0.2; nesse caso, troque o
createRootdos exemplos porReactDOM.render. - Tailwind CSS v3.4 ou mais novo no v3. O Medway UI usa utilitários do 3.4 (como
size-*) e não funciona com o Tailwind v4. - Os tipos de
@medway-ui/core/tokense@medway-ui/core/note-editorfuncionam com qualquermoduleResolutiondo TypeScript a partir do@medway-ui/core1.36.1. Em versões anteriores, use"bundler","node16"ou"nodenext"; projetos novos de Next.js e Vite já vêm assim.
1. Instale os pacotes
Instale as ferramentas de CSS e depois os componentes e os ícones:
yarn add -D tailwindcss@^3.4 tailwindcss-animate postcss postcss-import autoprefixer
yarn add @medway-ui/core @medway-ui/iconsSe o projeto foi criado com o Tailwind v4 (o create-next-app oferece essa opção), remova o @tailwindcss/postcss e qualquer @import "tailwindcss" do CSS antes de seguir.
2. Configure o PostCSS
Crie o postcss.config.mjs na raiz do projeto. Sem a configuração do PostCSS o Tailwind não roda: o build termina sem erro, mas os componentes aparecem sem estilo.
const config = {
plugins: {
"postcss-import": {},
tailwindcss: {},
autoprefixer: {},
},
};
export default config;O postcss-import precisa vir primeiro. É ele que junta os tokens do Medway UI ao seu CSS antes de o Tailwind processar o arquivo.
Se o projeto já tem uma configuração de PostCSS (postcss.config.js, .postcssrc ou a chave postcss no package.json), edite essa em vez de criar outra. O build usa só a primeira que encontra e ignora as demais sem avisar.
3. Configure o Tailwind
Crie o tailwind.config.ts na raiz do projeto (ou edite o que já existe, pelo mesmo motivo do passo 2):
import type { Config } from "tailwindcss";
import animate from "tailwindcss-animate";
import {
animation,
borderRadius,
boxShadow,
colors,
content,
fontFamily,
fontSize,
fontWeight,
keyframes,
lineHeight,
screens,
} from "@medway-ui/core/tokens";
const config: Config = {
darkMode: ["class"],
content: [
...content,
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
colors,
fontFamily,
fontSize,
fontWeight,
lineHeight,
borderRadius,
boxShadow,
keyframes,
animation,
},
screens,
},
plugins: [animate],
};
export default config;O que cada parte faz:
content: ocontentdo Medway UI já inclui os componentes do pacote (em./node_modules/@medway-ui/core) e a pasta./src. Acrescente as pastas do seu projeto que ficam fora desrc/, como as./appe./componentsdo Next.js no exemplo. Em monorepo, se o pacote ficar nonode_modulesda raiz, acrescente também o caminho aténode_modules/@medway-ui/core/dist/**/*.{js,mjs}a partir da pasta do app.darkMode: ["class"]: o tema escuro é ativado pela classedarkno<html>. Veja Tema escuro.screens: substitui os breakpoints padrão do Tailwind pelos do Medway UI:mobileesm(320px),tabletemd(600px),desktopelg(1024px),tvexl(1920px).
4. Configure o CSS global
No CSS global do projeto (app/globals.css no Next.js, src/index.css no Vite), apague o conteúdo que veio do template e deixe assim:
@import "@medway-ui/core/tokens.css";
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
* {
@apply border-gray-stroke-muted;
}
body {
@apply bg-background text-foreground antialiased;
}
}O @import dos tokens precisa ser a primeira linha do arquivo, antes das diretivas @tailwind. Importe os tokens sempre por esse @import no CSS, nunca por um import no JavaScript.
Apague o CSS do template porque ele sobrescreve o Medway UI: o do create-next-app define font-family: Arial no body, e o do Vite muda o tamanho base da fonte e centraliza o #root.
5. Carregue a fonte Montserrat
O Medway UI usa a Montserrat nos pesos 400, 500, 600, 700, 800 e 900. Os tokens procuram a fonte pela variável --font-sans.
Next.js: carregue com o next/font, usando --font-sans como variável, e aplique a classe font-sans no body. O layout completo está no passo 6.
Vite e CRA: adicione a fonte do Google Fonts no <head> do index.html:
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700;800;900&display=swap"
rel="stylesheet"
/>6. Monte a raiz do app
A raiz do app importa o CSS global e monta dois providers:
TooltipProvider: envolve a árvore. Sem ele, oTooltiplança o erro "Tooltipmust be used withinTooltipProvider".ToastProvider: renderiza os toasts. Sem ele, chamar otoast()que vem douseToastnão mostra nada.
Next.js: o app/layout.tsx completo, já com a fonte do passo 5:
import "./globals.css";
import { ToastProvider, TooltipProvider } from "@medway-ui/core";
import { Montserrat } from "next/font/google";
import type { ReactNode } from "react";
const montserrat = Montserrat({
weight: ["400", "500", "600", "700", "800", "900"],
subsets: ["latin"],
display: "swap",
variable: "--font-sans",
});
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="pt-BR" suppressHydrationWarning>
<body className={`${montserrat.variable} font-sans`}>
<TooltipProvider>{children}</TooltipProvider>
<ToastProvider />
</body>
</html>
);
}Os componentes já são client components, então podem ser usados direto em Server Components. O useToast e os callbacks de eventos (onClick etc.) continuam pedindo um arquivo com "use client".
Vite e CRA: o src/main.tsx (ou src/index.tsx no CRA):
import "./index.css";
import { ToastProvider, TooltipProvider } from "@medway-ui/core";
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<TooltipProvider>
<App />
</TooltipProvider>
<ToastProvider />
</StrictMode>
);O Sidebar já traz um TooltipProvider dentro do SidebarProvider. Mesmo assim, mantenha o provider na raiz: os tooltips fora da sidebar precisam dele.
7. Tema escuro
Os tokens trocam de valor quando o <html> tem a classe dark. No Next.js, o jeito mais simples é o next-themes (yarn add next-themes ou npm install next-themes). No app/layout.tsx do passo 6, importe o ThemeProvider de next-themes e envolva o conteúdo do <body>:
<body className={`${montserrat.variable} font-sans`}>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<TooltipProvider>{children}</TooltipProvider>
<ToastProvider />
</ThemeProvider>
</body>O suppressHydrationWarning no <html>, que já está no layout do passo 6, é necessário com o next-themes. Sem ele, adicione ou remova a classe dark do document.documentElement.
8. Use os componentes
import { Button } from "@medway-ui/core";
import { CircleCheck } from "@medway-ui/icons";
export function Exemplo() {
return <Button iconLeft={<CircleCheck />}>Salvar</Button>;
}A partir do @medway-ui/icons 1.17.0, o ícone é decorativo por padrão (aria-hidden="true"): no exemplo, o leitor de tela lê só "Salvar". Quando o ícone sozinho tem significado, passe aria-label, como na acessibilidade dos ícones.
O NoteEditor tem um ponto de entrada separado, @medway-ui/core/note-editor, e dependências próprias. Veja a instalação do Note Editor.
Notas por framework
Next.js
Siga os passos como estão. Se o projeto usa src/app, as linhas ./app/** e ./components/** do content podem sair, porque o content do Medway UI já cobre ./src.
Vite
O content do Medway UI já cobre ./src, então o content do tailwind.config.ts fica só content: [...content]. O template do Vite também traz um src/App.css; ele só estiliza o exemplo do template e pode ser apagado junto com o import no App.tsx.
Create React App (craco)
O CRA ignora o postcss.config do projeto e só ativa o Tailwind quando encontra um tailwind.config.js. Por isso, no CRA:
- Use o
tailwind.config.jsem CommonJS, com o mesmo conteúdo do passo 3:
const animate = require("tailwindcss-animate");
const {
animation,
borderRadius,
boxShadow,
colors,
content,
fontFamily,
fontSize,
fontWeight,
keyframes,
lineHeight,
screens,
} = require("@medway-ui/core/tokens");
module.exports = {
darkMode: ["class"],
content: [...content],
theme: {
extend: {
colors,
fontFamily,
fontSize,
fontWeight,
lineHeight,
borderRadius,
boxShadow,
keyframes,
animation,
},
screens,
},
plugins: [animate],
};- Use o
postcss.config.jsem CommonJS, com os mesmos plugins do passo 2:
module.exports = {
plugins: {
"postcss-import": {},
tailwindcss: {},
autoprefixer: {},
},
};- Faça o craco ler esse arquivo de PostCSS. Sem isso, o
postcss-importnão roda e o build quebra com o erro de@layer components:
module.exports = {
style: {
postcss: { mode: "file" },
},
};Projetos com estilos legados
Para projetos que já têm MUI, styled-components ou um tema próprio do Tailwind:
- Mantenha o preflight do Tailwind. Os componentes dependem do reset dele, por exemplo para as bordas aparecerem. Se algum estilo antigo brigar com o reset, ajuste o estilo antigo em vez de desligar o preflight.
- Breakpoints: o
screensdo Medway UI muda o valor desm,md,lgexl. Revise as classes responsivas que já existem antes de trocar os breakpoints. Se precisar manter breakpoints próprios, adicione com outros nomes ao lado dos do Medway UI. - Cores e fontes do tema antigo: coloque os tokens do Medway UI em
theme.extend, como no passo 3. Assim as classes que já existem continuam funcionando. - Outros
@importde CSS: opostcss-importprocura os arquivos direto nonode_modules, sem ler o campoexportsdos pacotes. Se um@importde CSS de outro pacote passar a falhar com "Failed to find", troque pelo caminho real do arquivo dentro do pacote.
Vindo da versão anterior deste guia
Projetos configurados com a versão anterior desta página precisam de quatro ajustes:
- PostCSS: adicione o
postcss-importantes dotailwindcss, como no passo 2, e atualize o@medway-ui/corepara a 1.35.1 ou superior. Projetos que importavam@medway-ui/core/dist/assets/css/tokens.csspodem voltar para@medway-ui/core/tokens.css. - Fonte: troque
variable: "--font-montserrat"porvariable: "--font-sans"e aplique a classefont-sansnobody, como no passo 6. Com a variável antiga, a fonte depende do nome que onext/fontdá à família e pode cair na fonte do sistema. - Container: troque o bloco
container: { center: true, screens: { ...container } }dotailwind.configporcontainer: { center: true }. Ocontainerdos tokens gera media queries inválidas, que o Vite 8 trata como erro de build. Mantenha ocenter: truese o app usa a classecontainer. - CSS global: a versão anterior trazia regras para
mainebody(largura máxima, padding,overflow-x-hidden). Elas não fazem mais parte do guia; se o layout do app depende delas, mantenha-as no CSS do app.
No React Native, troque primeiro o withNativeWind por withMedwayUi no metro.config.js (passo 2) e só depois apague o global.css que importava @medway-ui/core/tokens.css: os tokens agora vêm do MedwayUiProvider. Na ordem inversa, o Metro procura o arquivo apagado e o bundle quebra.
Problemas comuns
| Sintoma | Causa | Correção |
|---|---|---|
| Componentes sem estilo, mas o build passa | Falta a configuração do PostCSS, existe outra configuração que o build lê primeiro, ou o content não cobre os arquivos | Revise os passos 2 e 3 |
Failed to find '@medway-ui/core/tokens.css' | @medway-ui/core anterior à 1.35.1 | Atualize o @medway-ui/core. Se não der, no Next.js e no CRA importe @medway-ui/core/dist/assets/css/tokens.css (no Vite esse caminho não funciona e não é necessário) |
`@layer components` is used but no matching `@tailwind components` directive is present | O tokens.css foi processado sozinho, sem o postcss-import | Coloque o postcss-import antes do tailwindcss no PostCSS, importe os tokens pelo @import no mesmo arquivo das diretivas @tailwind e, no CRA, use o mode: "file" do craco |
Invalid media query: (min-width: var(--container-...)) | container.screens com o token container | Troque o bloco por container: { center: true } |
| A fonte não é a Montserrat | Fonte não carregada, ou o CSS do template sobrescreve o body | Revise os passos 4 a 6 |
`Tooltip` must be used within `TooltipProvider` | Falta o provider em volta da árvore | Revise o passo 6 |
O toast() do useToast não mostra nada | Falta o ToastProvider | Revise o passo 6 |
Cannot find module '@medway-ui/core/tokens' (ou /note-editor) no TypeScript | @medway-ui/core anterior à 1.36.1 com moduleResolution antigo ("node") | Atualize o @medway-ui/core, ou use "bundler", "node16" ou "nodenext" no tsconfig.json |
React Native (Expo)
O @medway-ui/native tem a mesma API visual do @medway-ui/core, construída sobre NativeWind 4 e @rn-primitives.
Há dois modos de usar:
- App existente (brownfield): o NativeWind fica restrito aos componentes do Medway UI e às pastas de telas que você escolher. O resto do app continua com o sistema de estilos que já tem.
- App novo (greenfield): o NativeWind vale para o app inteiro.
Os passos 1 a 3 são iguais nos dois modos. O passo 4 muda conforme o modo.
1. Instale as dependências
npm install @medway-ui/native @medway-ui/core @medway-ui/icons nativewind@^4 tailwindcss@^3.4 clsx tailwind-merge class-variance-authority
npx expo install react-native-reanimated react-native-svg react-native-safe-area-context expo-font @react-navigation/native @react-navigation/native-stack- O
@medway-ui/coreentra só para fornecer os tokens aotailwind.config.js. No app não se importa o CSS dele. - O
nativewindprecisa ser da versão 4 e otailwindcssda versão 3: as versões seguintes dos dois não funcionam com o Medway UI. - O
@react-navigation/nativee o@react-navigation/native-stacksão necessários porque o@medway-ui/native/setup, de onde vem oMedwayUiProvider, importa os dois. Apps com React Navigation ou Expo Router normalmente já têm. - O
react-native-safe-area-contexté usado peloToastProviderpara respeitar a área segura do topo.
Dependências opcionais, conforme o que o app usa:
| Pacotes | Para quê |
|---|---|
expo-linear-gradient e @react-native-masked-view/masked-view | Gradientes do Toast e o anel animated do IndexLevel. Sem eles, viram cor sólida |
react-native-webview | NoteEditor (@medway-ui/native/note-editor). O @medway-ui/notes-core já vem com o @medway-ui/native |
Instale os opcionais com npx expo install, para alinhar as versões ao SDK do Expo.
2. Configure o Metro
const { getDefaultConfig } = require("expo/metro-config");
const { withMedwayUi } = require("@medway-ui/native/metro");
module.exports = withMedwayUi(getDefaultConfig(__dirname), {
projectRoot: __dirname,
});Use o withMedwayUi nos dois modos, em vez do withNativeWind. Ele liga o NativeWind com o CSS de tokens do pacote e resolve os imports internos da biblioteca. Não é preciso criar um global.css no app.
3. Registre as fontes
No React Native, cada peso da Montserrat é uma família com nome próprio. Instale as fontes:
npx expo install @expo-google-fonts/montserratE crie um arquivo com os nomes que o Medway UI espera:
import {
Montserrat_300Light,
Montserrat_400Regular,
Montserrat_500Medium,
Montserrat_600SemiBold,
Montserrat_700Bold,
Montserrat_800ExtraBold,
Montserrat_900Black,
} from "@expo-google-fonts/montserrat";
export const medwayUiFonts = {
Montserrat: Montserrat_400Regular,
MontserratLight: Montserrat_300Light,
MontserratMedium: Montserrat_500Medium,
MontserratSemiBold: Montserrat_600SemiBold,
MontserratBold: Montserrat_700Bold,
MontserratExtraBold: Montserrat_800ExtraBold,
MontserratBlack: Montserrat_900Black,
};Passe o medwayUiFonts para o useFonts da raiz do app, antes de renderizar as telas. Se o app já carrega fontes, junte os dois objetos no mesmo useFonts. Se o app já tem os arquivos .ttf, pode registrar esses arquivos com os mesmos nomes.
4. Configure o modo do app
App existente (brownfield)
tailwind.config.js: liste só as pastas do app que usam className. Os componentes do pacote já entram no content automaticamente.
const { createMedwayUiTailwindConfig } = require("@medway-ui/native/tailwind");
module.exports = createMedwayUiTailwindConfig({
projectRoot: __dirname,
content: ["./src/pages/medway-ui/**/*.{ts,tsx}"],
});babel.config.js: o preset do app não liga o NativeWind. O createBrownfieldOverride liga o NativeWind só dentro da biblioteca e das pastas src/pages/<appPagesDir>/:
const { createBrownfieldOverride } = require("@medway-ui/native/babel-brownfield");
module.exports = function (api) {
api.cache(true);
return {
presets: ["babel-preset-expo"],
plugins: [
/* plugins atuais do app, sem o do reanimated */
],
overrides: [
createBrownfieldOverride({
projectRoot: __dirname,
appPagesDir: "medway-ui",
plugins: ["react-native-reanimated/plugin"],
}),
],
};
};appPagesDiré o nome da pasta dentro desrc/pages. Para ligar o NativeWind em mais de uma pasta, adicione umcreateBrownfieldOverridepara cada uma e inclua as mesmas pastas nocontentdo Tailwind.- O
react-native-reanimated/pluginprecisa ser o último plugin do override. - Se o app tem aliases
@/nobabel-plugin-module-resolver, use ocreateMedwayUiAliasResolvernoresolvePathdo module-resolver. Sem ele, os imports internos da biblioteca resolvem para osrc/do app. O exemplo completo está no README do@medway-ui/native.
MedwayUiProvider: toda tela que usa o Medway UI precisa estar dentro dele. Ele carrega o CSS dos tokens, aplica o tema e monta o portal usado por Select, Dialog, Popover, Tooltip, Sheet e DropdownMenu:
import { MedwayUiProvider } from "@medway-ui/native/setup";
import type { ReactNode } from "react";
interface TelaMedwayUiProps {
temaEscolhido?: "light" | "dark";
children: ReactNode;
}
export function TelaMedwayUi({ temaEscolhido, children }: TelaMedwayUiProps) {
return (
<MedwayUiProvider colorScheme={temaEscolhido}>{children}</MedwayUiProvider>
);
}- Em
colorScheme, passe o tema que o usuário escolheu no app ("light"ou"dark"), vindo de onde o app guarda essa preferência (Redux, Restyle etc.). Quando o app segue o tema do sistema, não passecolorScheme: o provider usatheme="system"e não mexe no tema. - Não calcule o
colorSchemecom ouseColorSchemedo React Native. O NativeWind aplica o tema peloAppearance, que vale para o app inteiro: quando ocolorSchemedifere do tema em vigor, oMedwayUiProvidertroca o tema do app enquanto está montado (e ouseColorSchemepassa a devolver esse valor), e devolve o controle ao sistema quando desmonta. - Monte o
<ToastProvider />(de@medway-ui/native) uma vez só, dentro de umMedwayUiProviderque fique sempre montado, perto da raiz do app. CadaToastProvidermontado mostra todos os toasts: se cada tela tiver o seu, telas empilhadas repetem o mesmo toast.
App novo (greenfield)
tailwind.config.js:
const { createMedwayUiTailwindConfig } = require("@medway-ui/native/tailwind");
module.exports = createMedwayUiTailwindConfig({
projectRoot: __dirname,
content: ["./app/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}"],
});babel.config.js:
module.exports = function (api) {
api.cache(true);
return {
presets: [
["babel-preset-expo", { jsxImportSource: "nativewind" }],
"nativewind/babel",
],
plugins: ["react-native-reanimated/plugin"],
};
};Layout raiz: carregue as fontes do passo 3 e envolva o app com o MedwayUiProvider e o ToastProvider:
import { useFonts } from "expo-font";
import { ToastProvider } from "@medway-ui/native";
import { MedwayUiProvider } from "@medway-ui/native/setup";
import { Stack } from "expo-router";
import { medwayUiFonts } from "../medway-ui-fonts";
export default function RootLayout() {
const [fontsLoaded] = useFonts(medwayUiFonts);
if (!fontsLoaded) return null;
return (
<MedwayUiProvider theme="system">
<Stack />
<ToastProvider />
</MedwayUiProvider>
);
}5. Reinicie o Metro
Sempre que mudar o metro.config.js, o babel.config.js ou o tailwind.config.js, reinicie o Metro com o cache limpo:
npx expo start --clearDepois, importe os componentes:
import { Button, Input, Alert, Badge } from "@medway-ui/native";Para outros erros comuns, veja a solução de problemas no README do @medway-ui/native.