Pular para o conteúdo
UI

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árioSiga
Web com Next.js (App Router)Web, com as notas de Next.js
Web com ViteWeb, 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 guiaVindo 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 NativeWindReact Native, modo app novo

Web

Requisitos:

  • @medway-ui/core 1.35.1 ou superior.
  • React 18 ou superior. O pacote ainda aceita o React 17.0.2; nesse caso, troque o createRoot dos exemplos por ReactDOM.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/tokens e @medway-ui/core/note-editor funcionam com qualquer moduleResolution do TypeScript a partir do @medway-ui/core 1.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/icons

Se 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.

postcss.config.mjs
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):

tailwind.config.ts
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: o content do 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 de src/, como as ./app e ./components do Next.js no exemplo. Em monorepo, se o pacote ficar no node_modules da 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 classe dark no <html>. Veja Tema escuro.
  • screens: substitui os breakpoints padrão do Tailwind pelos do Medway UI: mobile e sm (320px), tablet e md (600px), desktop e lg (1024px), tv e xl (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:

globals.css
@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:

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, o Tooltip lança o erro "Tooltip must be used within TooltipProvider".
  • ToastProvider: renderiza os toasts. Sem ele, chamar o toast() que vem do useToast não mostra nada.

Next.js: o app/layout.tsx completo, já com a fonte do passo 5:

app/layout.tsx
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):

src/main.tsx
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:

  1. Use o tailwind.config.js em CommonJS, com o mesmo conteúdo do passo 3:
tailwind.config.js
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],
};
  1. Use o postcss.config.js em CommonJS, com os mesmos plugins do passo 2:
postcss.config.js
module.exports = {
  plugins: {
    "postcss-import": {},
    tailwindcss: {},
    autoprefixer: {},
  },
};
  1. Faça o craco ler esse arquivo de PostCSS. Sem isso, o postcss-import não roda e o build quebra com o erro de @layer components:
craco.config.js
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 screens do Medway UI muda o valor de sm, md, lg e xl. 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 @import de CSS: o postcss-import procura os arquivos direto no node_modules, sem ler o campo exports dos pacotes. Se um @import de 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-import antes do tailwindcss, como no passo 2, e atualize o @medway-ui/core para a 1.35.1 ou superior. Projetos que importavam @medway-ui/core/dist/assets/css/tokens.css podem voltar para @medway-ui/core/tokens.css.
  • Fonte: troque variable: "--font-montserrat" por variable: "--font-sans" e aplique a classe font-sans no body, como no passo 6. Com a variável antiga, a fonte depende do nome que o next/font dá à família e pode cair na fonte do sistema.
  • Container: troque o bloco container: { center: true, screens: { ...container } } do tailwind.config por container: { center: true }. O container dos tokens gera media queries inválidas, que o Vite 8 trata como erro de build. Mantenha o center: true se o app usa a classe container.
  • CSS global: a versão anterior trazia regras para main e body (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

SintomaCausaCorreção
Componentes sem estilo, mas o build passaFalta a configuração do PostCSS, existe outra configuração que o build lê primeiro, ou o content não cobre os arquivosRevise os passos 2 e 3
Failed to find '@medway-ui/core/tokens.css'@medway-ui/core anterior à 1.35.1Atualize 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 presentO tokens.css foi processado sozinho, sem o postcss-importColoque 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 containerTroque o bloco por container: { center: true }
A fonte não é a MontserratFonte não carregada, ou o CSS do template sobrescreve o bodyRevise os passos 4 a 6
`Tooltip` must be used within `TooltipProvider`Falta o provider em volta da árvoreRevise o passo 6
O toast() do useToast não mostra nadaFalta o ToastProviderRevise 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/core entra só para fornecer os tokens ao tailwind.config.js. No app não se importa o CSS dele.
  • O nativewind precisa ser da versão 4 e o tailwindcss da versão 3: as versões seguintes dos dois não funcionam com o Medway UI.
  • O @react-navigation/native e o @react-navigation/native-stack são necessários porque o @medway-ui/native/setup, de onde vem o MedwayUiProvider, importa os dois. Apps com React Navigation ou Expo Router normalmente já têm.
  • O react-native-safe-area-context é usado pelo ToastProvider para respeitar a área segura do topo.

Dependências opcionais, conforme o que o app usa:

PacotesPara quê
expo-linear-gradient e @react-native-masked-view/masked-viewGradientes do Toast e o anel animated do IndexLevel. Sem eles, viram cor sólida
react-native-webviewNoteEditor (@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

metro.config.js
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/montserrat

E crie um arquivo com os nomes que o Medway UI espera:

medway-ui-fonts.ts
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.

tailwind.config.js
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>/:

babel.config.js
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 de src/pages. Para ligar o NativeWind em mais de uma pasta, adicione um createBrownfieldOverride para cada uma e inclua as mesmas pastas no content do Tailwind.
  • O react-native-reanimated/plugin precisa ser o último plugin do override.
  • Se o app tem aliases @/ no babel-plugin-module-resolver, use o createMedwayUiAliasResolver no resolvePath do module-resolver. Sem ele, os imports internos da biblioteca resolvem para o src/ 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 passe colorScheme: o provider usa theme="system" e não mexe no tema.
  • Não calcule o colorScheme com o useColorScheme do React Native. O NativeWind aplica o tema pelo Appearance, que vale para o app inteiro: quando o colorScheme difere do tema em vigor, o MedwayUiProvider troca o tema do app enquanto está montado (e o useColorScheme passa a devolver esse valor), e devolve o controle ao sistema quando desmonta.
  • Monte o <ToastProvider /> (de @medway-ui/native) uma vez só, dentro de um MedwayUiProvider que fique sempre montado, perto da raiz do app. Cada ToastProvider montado mostra todos os toasts: se cada tela tiver o seu, telas empilhadas repetem o mesmo toast.

App novo (greenfield)

tailwind.config.js:

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:

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:

app/_layout.tsx
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 --clear

Depois, 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.