
Como conectamos design e código através de padrões rigorosos de engenharia para escalar interfaces no iFood
No iFood, enfrentávamos um problema comum em organizações de tecnologia em escala: uma divergência crescente entre design e código. O que começou como pequenas inconsistências visuais evoluiu para um gap significativo entre a intenção do design e a implementação final.
Ficou claro que não precisávamos apenas de uma atualização de pacote – precisávamos de uma mudança estrutural em como pensamos sobre UI na engenharia. A resposta para esse desafio? iFood Design System (IFDS).
IFDS não é apenas um UI Kit bonito no Figma ou uma pasta solta de componentes. É um ecossistema abrangente de engenharia construído para escalar nossa linguagem de design em todo o iFood com rigor técnico.
Neste post, vamos revelar os bastidores e detalhar nossa arquitetura de monorepo, organização estratégica de pacotes e os padrões rigorosos de componentização que adotamos.
Para servir múltiplas plataformas (Web, Android, iOS, Flutter) sem duplicar lógica de design, dividimos nossa arquitetura em duas partes conceituais:
Essa separação garante que quando a cor primária da marca muda no iFDL, a mudança se propaga via CI/CD, que é Continuous Integration/Continuous Delivery (um processo que automatiza a integração e a distribuição de software), para todas as plataformas consumidoras automaticamente.
Design Tokens não são apenas valores estáticos em um arquivo JSON – eles precisam ser transformados em formatos específicos para cada plataforma consumidora. No IFDS, essa transformação é automatizada através do Style-Dictionary, uma ferramenta da Amazon que gera temas para múltiplas plataformas a partir de uma única fonte de verdade.
Veja como funciona:
--ifdl-color-brand-primary-default);IFDL.color.brand.primary.default);Isso significa que quando um designer muda a cor primária da marca no Figma, essa mudança se propaga automaticamente para todas as plataformas sem um único engenheiro tocar no código.
O style-dictionary-builder é configurado para gerar temas para múltiplas organizações (iFood, Pago, por exemplo), múltiplos temas (default, dark) e múltiplos modos (light, dark), todos da mesma fonte de tokens. Arquivos gerados são somente leitura e nunca devem ser editados manualmente. Eles são regenerados a cada mudança de token.
Gerenciar um sistema dessa magnitude requer ferramentas robustas. Escolher um monorepo moderno (usando Nx, que é um sistema de compilação de monorepos – ferramenta de orquestração de monorepos focada em performance e escalabilidade de grandes projetos de software) não foi apenas sobre colocar código em um único repo, foi sobre ganhar eficiência operacional em escala.
Com Nx, ganhamos superpoderes em nosso pipeline CI/CD:
Como organizamos fisicamente centenas de componentes? Definimos uma hierarquia clara de três níveis que separa fundamentos, padrões globais e implementações específicas de produto:
ifds/components/
├── shared/ # Reusable fundamentals (Atoms/Molecules)
│ └── web/
│ └── src/
│ ├── Buttons/
│ ├── Form/
│ ├── Layout/
│ ├── Typography/
│ ├── hooks/ # Shared utility hooks
│ └── utils/ # Shared helper functions
│
├── global/ # Corporate patterns (Organisms)
│ └── web/
│ └── src/
│ ├── Communication/ # e.g. Toasts, Banners
│ ├── Navigation/ # e.g. Sidebars, Headers
│ ├── Rating/ # e.g. Rating stars
│
└── product/ # Business domain-specific
└── driver-app/ # e.g. Components that only exist in Driver App
└── web/
└── src/
A Estratégia de Três Níveis
Esta estrutura reflete uma decisão estratégica sobre o escopo e granularidade de reutilização:
Esta divisão previne que componentes fundamentais sejam “poluídos” com regras de negócio e garante que padrões complexos permaneçam consistentes entre produtos.
Passando do nível de monorepo para o nível de arquivo, a padronização continua. No IFDS, criar um componente não é apenas sobre criar um arquivo .tsx.
Definimos um padrão rigoroso para o que constitui um “Standard Component”. Cada componente requer uma estrutura completa para garantir que implementação, testes, documentação, estilos e tipos sejam todos cidadãos de primeira classe.
Aqui está a estrutura real de um componente simples da camada shared, como um Button:
Button/
├── Button.tsx # Main implementation [required]
├── Button.test.tsx # Unit/a11y tests (Vitest/RTL) [required]
├── Button.stories.tsx # Storybook stories [required]
├── Button.module.css # Isolated styles (CSS Modules) [required]
├── types.ts # TypeScript definitions and public interfaces [required]
├── index.ts # Public API of the package
Esta padronização elimina suposições. Qualquer engenheiro se juntando ao projeto sabe exatamente onde encontrar lógica, estilos ou testes.
Ter uma estrutura de pastas organizada não é suficiente. O que realmente garante qualidade e manutenção são os padrões de código que aplicamos dentro desses arquivos.
1. TypeScript Rigoroso e Semântico
TypeScript não é opcional. No IFDS, levamos tipagem a sério para garantir excelente Developer Experience (DX) e prevenir bugs.
Nossas propriedades são semânticas: elas descrevem comportamento ou intenção (variant="primary", isLoading…), nunca valores de estilo diretos (color="red", fontSize="14px"...). Isso nos dá flexibilidade para mudar o design sem quebrar as aplicações consumidoras.
Usamos composição de tipos em nosso arquivo types.ts, separando nossas propriedades customizadas dos atributos nativos do HTML. Além disso, envolvemos todas as propriedades com o utility type Prettify para melhorar a experiência de autocomplete no TypeScript:
// types.ts (Real IFDS example)
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
import { Prettify } from '../../utils';
// Semantic props from our Design System
export type CustomButtonProps = {
children?: ReactNode;
variant?: 'primary' | 'secondary' | 'text' | 'link';
isLoading?: boolean;
};
// Final composition with native HTML button props, excluding what we override
// Prettify improves autocomplete experience in TypeScript
export type ButtonProps = Prettify<
CustomButtonProps & Omit<ComponentPropsWithoutRef<'button'>, keyof CustomButtonProps>
>;
2. Estilo: CSS Módulos + Tokens Mandatórios
O maior inimigo da consistência em um Design System? “Magic numbers” no CSS. No IFDS, usamos CSS Modules para garantir escopo isolado e evitar conflitos de nomes de classe em micro-frontends. Mas a regra de ouro é: sempre usar design tokens, nunca valores hardcoded.
Esses tokens são gerados do Figma pelo iFDL via style-dictionary. Engenheiros não “criam” cores, nós as consumimos.
Wrong (Hardcoded):
.button {
padding: 16px;
background-color: #ea1d2c; /* iFood's red today, maybe not tomorrow */
border-radius: 8px;
}
Right (Tokenized):
/* Button.module.css */
.button {
border-radius: var(--ifdl-border-radius-re);
padding: var(--ifdl-spacing-scale-12);
font-family: var(--ifdl-font-family-ifood-body);
&.variant-primary {
&.state-disabled {
color: var(--ifdl-background-color-on-disabled);
background-color: var(--ifdl-background-color-disabled);
}
&:not(.state-disabled) {
color: var(--ifdl-background-color-on-brand-primary);
--background-color: var(--ifdl-background-color-brand-primary);
}
}
}
3. Padrão de Arquitetura: Inversão de Controle (IoC) e Composição
Para criar componentes verdadeiramente reutilizáveis em escala, precisamos evitar o “React Prop Drilling Hell” – onde um componente pai recebe dezenas de props apenas para passá-las para filhos internos. Adotamos Inversion of Control (IoC) através do Composition pattern. Em vez de um componente monolítico que tenta fazer tudo via configuration props, oferecemos sub-componentes que os desenvolvedores montam.
Isso permite flexibilidade mantendo consistência visual. Um exemplo clássico é nosso componente Dialog:
// Composition example (IoC)
// The parent Dialog component doesn't need to know what's inside Footer.
<Dialog status="open" onClose={close}>
<Dialog.Header title="Confirmation" />
<Dialog.Body>
Do you really want to delete this item?
</Dialog.Body>
<Dialog.Footer>
<Button variant="tertiary" onClick={close}>Cancel</Button>
<Button variant="primary" onClick={confirm}>Confirm</Button>
</Dialog.Footer>
</Dialog>
Finalmente, a padronização se estende à garantia de qualidade no pipeline. Um componente no IFDS só é considerado “pronto” e mergeado se incluir:
Migrar de uma legacy library para o IFDS exigiu um compromisso sério com engenharia de software. Esta organização, desde a macroestrutura do monorepo com Nx, passando pela divisão estratégica entre shared, global e product, até a microestrutura de arquivos e padrões TypeScript/CSS, não é burocracia. É o que garante a consistência, manutenibilidade e escalabilidade de que precisamos para dezenas de product teams no iFood construírem interfaces rapidamente sem reinventar a roda.
O pipeline automatizado de tokens, do Figma ao código, através do style-dictionary, garante que mudanças de design se propaguem automaticamente para todas as plataformas, eliminando drift entre design e implementação.
Agora que estabelecemos a base técnica, nosso foco se volta para a adoção e evolução contínua do sistema. Como vocês organizam a distinção entre componentes genéricos e product-specific components em escala? Compartilhem nos comentários!


Software Engineering
Augusto Sandim trabalha como Engenheiro de Software no iFood. Ele é graduado em Ciência da Computação, é do Mato Grosso do Sul e adora andar de BMX.
Estamos sempre em busca de desenvolvedores, designers e cientistas de dados apaixonados para nos ajudar a revolucionar a experiência de entrega de alimentos. Junte-se à iFood Tech e faça parte da construção do futuro da tecnologia alimentar.
Conheça nossas CarreirasCada artigo é resultado da visão e expertise dos nossos autores. Veja quem contribui com nosso blog: