DEV Community

Dailson Igo Araujo Palheta
Dailson Igo Araujo Palheta

Posted on • Edited on

Ruby on Rails 8 - Frontend Rápido Usando Tailwind como um Frameworks CSS Classless

Este artigo é intencionalmente muito semelhante aos anteriores desta série, mas desta vez usaremos o framework Tailwind como um framework css classless. Ele foi inspirado no artigo Classless CSS based on Tailwind

Inicie um novo aplicativo Rails

  • O time antes do comando rails serve para exibir no final da execução do comando o seu tempo de execução. No exemplo abaixo, levou 47 segundos.
$ rails -v
Rails 8.0.0

$ time rails new classless-css-tailwind
...
real    0m47.500s
user    0m33.052s
sys     0m4.249s
Enter fullscreen mode Exit fullscreen mode

O Rails 8, dentro de sua filosofia No Build, utilizará por padrão o Propshaft como biblioteca de pipeline de assets e o Importmap como biblioteca para JavaScript. O Importmap não realiza nenhum tipo de processamento do JavaScript.

Abra o projeto com o VSCode ou seu editor preferido

$ cd classless-css-tailwind && code .
Enter fullscreen mode Exit fullscreen mode

Criando algumas páginas para visualizar a estilização dos elementos HTML

As páginas estão nos Passos Comuns no primeiro artigo da série.

Altere o arquivo do Tailwind app/assets/stylesheets/application.tailwind.css

Exibir mais …
Altere o arquivo acima para incluir a referência aos arquivos com a estilização em Tailwind CSS. Observe que apenas o Opção 1 está descomentada.
/* INSIRA OS CSS CUSTOMIZADOS DO TAILWIND NA PARTE SUPERIOR */
/* SE O "@tailwind base", "@tailwind components" E "@tailwind utilities" NÃO ESTIVEREM COMENTADOS */

/* Opção 1: Verde */
@import "./custom_tailwind/custom1.css";

/* Opção 2: Azul */
/* @import "./custom_tailwind/custom2.css"; */

/* Opção3: Do artigo "Classless CSS based on Tailwind" */
/* https://medium.com/@AntonShevchuk/classless-css-based-on-tailwind-57d4ef745c1f */
/* @import "./custom_tailwind/custom3.css"; */

/* @tailwind base;
@tailwind components;
@tailwind utilities; */

Enter fullscreen mode Exit fullscreen mode

Crie a pasta custom_tailwind dentro do diretório app/assets/stylesheets/ para adicionar os arquivos customizados do Tailwind.

Insira o conteúdo do 1º arquivo Tailwind customizado em custom1.css

Exibir mais …
Crie o arquivo app/assets/stylesheets/custom_tailwind/custom1.css e copie o conteúdo a seguir.
/* 
  Visão geral:
    Unificação de variáveis de tema (ao invés de --background-light e --background-dark, temos apenas --background, e assim por diante).
    Redução de duplicações de @media (prefers-color-scheme: dark). Boa parte do tema escuro está centralizado no :root.
    Usamos variáveis no lugar das cores diretas e, em alguns pontos, aproveitar a nomenclatura do Tailwind.

    Caso utilize o modo escuro via classes (class="dark") em vez de prefers-color-scheme, 
    a lógica seria um pouco diferente (usando dark:bg-*, dark:text-*, etc.). 
    Mas, conforme as recomendações, mantivemos o @media (prefers-color-scheme: dark) para respeitar as preferências do usuário.


  1. Variáveis de tema unificadas
  Agora temos --background, --text e --accent (entre outras) em vez de --background-light, --background-dark, etc.
  Isso reduz a repetição e deixa o código mais fácil de manter.

  2. Menos repetições de @media (prefers-color-scheme: dark)
  Quase tudo para o tema escuro foi concentrado em um único bloco, dentro do :root.
  Assim, sempre que o usuário preferir o modo escuro, todas as variáveis são redefinidas.

  3. Uso de variáveis complementares
  Adicionamos --background-code, --border, --form-border e --focus-ring para garantir que todas as cores que possam variar 
  conforme o tema sejam facilmente alteradas.

  4. Estilos de formulário otimizados
  Em vez de separar cada tipo de input em vários blocos, unificamos a maioria deles.
  Evita duplicações e mantém uma consistência de design.

  ---
  Observações Finais

  Se quiser seguir ainda mais o padrão do Tailwind sem tantas variáveis, você poderia usar as classes utilitárias padrão 
  (bg-gray-50, text-gray-900, dark:bg-gray-800, dark:text-gray-100, etc.).
  Para quem prefere o modo escuro via classe .dark, bastaria trocar o @media (prefers-color-scheme: dark) 
  por seletores .dark & { ... } no arquivo e controlar o tema em JavaScript ou manualmente no HTML (<html class="dark">).

*/

/* 
 |-----------------------------------------------------------------------------
 | IMPORTA O TAILWIND CSS
 |-----------------------------------------------------------------------------
 | Aqui importamos as diretivas do Tailwind para carregar o CSS base, 
 | componentes e utilitários. Isso garante que todas as funcionalidades 
 | essenciais do Tailwind sejam carregadas antes de adicionarmos 
 | nossas customizações.
 */
@tailwind base;
@tailwind components;
@tailwind utilities;

/* 
 |-----------------------------------------------------------------------------
 | VARIÁVEIS CSS PARA TEMAS CLARO/ESCURO
 |-----------------------------------------------------------------------------
 | Agora, unificamos as variáveis para evitar duplicação. Em vez de termos
 | --background-light e --background-dark, temos apenas --background e 
 | mudamos seu valor no @media (prefers-color-scheme: dark).
 */
:root {
  /* Tema claro (default) */
  --background: #ffffff; /* Fundo do site */
  --text: #292929; /* Cor principal do texto */
  --accent: #1a8917; /* Cor de destaque (links, botões, etc.) */

  /* Variáveis complementares para uso em elementos específicos */
  --background-code: #f3f4f6; /* Fundo de blocos de código no claro */
  --border: #e5e7eb; /* Cor de borda padrão (claro) */
  --form-border: #d1d5db; /* Cor de borda padrão para formulários (claro) */
  --focus-ring: #1a8917; /* Cor do anel de foco (claro) */
}

@media (prefers-color-scheme: dark) {
  :root {
    /* Tema escuro */
    --background: #121212; /* Fundo do site */
    --text: #e6e6e6; /* Cor principal do texto */
    --accent: #4caf50; /* Cor de destaque (links, botões, etc.) */

    /* Variáveis complementares */
    --background-code: #1f2937; /* Fundo de blocos de código no escuro */
    --border: #374151; /* Cor de borda padrão (escuro) */
    --form-border: #4b5563; /* Cor de borda padrão para formulários (escuro) */
    --focus-ring: #4caf50; /* Cor do anel de foco (escuro) */
  }
}

/* 
 |-----------------------------------------------------------------------------
 | ESTILOS BASE
 |-----------------------------------------------------------------------------
 | A camada base (layer base) permite que possamos sobrescrever
 | estilos padrão do browser e aplicar resets ou estéticas iniciais.
 | Aqui fazemos o 'apply' de classes Tailwind diretamente em tags HTML 
 | para criar estilos globais.
 */
@layer base {
  /*
   |-----------------------------------------------------------------------------
   | HTML
   |-----------------------------------------------------------------------------
   | O <html> recebe o antialiased (que melhora a renderização de fontes),
   | além das cores de fundo e texto baseadas em nossas variáveis.
   */
  html {
    @apply antialiased;
    background-color: var(--background);
    color: var(--text);
  }

  /*
   |-----------------------------------------------------------------------------
   | BODY
   |-----------------------------------------------------------------------------
   | Aqui definimos o espaçamento interno (padding) para o corpo do site
   | e uma largura máxima de 4xl, centralizando (mx-auto) para que o 
   | conteúdo não fique muito extenso em telas grandes.
   */
  body {
    @apply mx-auto max-w-4xl px-4 leading-relaxed sm:px-6 lg:px-8;
  }

  /*
   |-----------------------------------------------------------------------------
   | TÍTULOS (H1, H2, H3, etc.)
   |-----------------------------------------------------------------------------
   | Definimos tamanhos de fonte diferentes para cada nível de título,
   | além de margens inferiores para separar visualmente do texto seguinte.
   | Também usamos breakpoints (sm:) para modificar o tamanho em telas maiores.
   */
  /* h1 {
    @apply mb-8 text-4xl font-bold leading-tight sm:text-5xl;
  }
  h2 {
    @apply mb-6 text-3xl font-bold leading-tight sm:text-4xl;
  }
  h3 {
    @apply mb-4 text-2xl font-bold sm:text-3xl;
  }
  h4 {
    @apply mb-4 text-xl font-bold;
  }
  h5 {
    @apply mb-4 text-lg font-bold;
  }
  h6 {
    @apply mb-4 text-base font-bold;
  } */

  /* Principais mudanças feitas:

  1. Removemos os breakpoints `sm:` fixos e substituímos por `clamp()`
  2. A função `clamp()` aceita três valores:
    - Valor mínimo (para telas pequenas)
    - Valor preferido (calculado usando viewport width)
    - Valor máximo (para telas grandes)

  3. A fórmula geral usada é:
    - Para títulos: percentual do viewport (vw) + valor base em rem
    - Para texto regular: valor menor do viewport + valor base menor

  4. Os valores foram escolhidos para criar uma transição suave entre:
    - Telas móveis (320px+)
    - Tablets (768px+)
    - Desktops (1024px+)
    - Telas grandes (1440px+)

  Esta implementação oferece várias vantagens:
  - Transição mais suave entre tamanhos de tela
  - Elimina "saltos" abruptos nos breakpoints
  - Mantém a legibilidade em todos os tamanhos de tela
  - Reduz a quantidade de código necessário
  - Proporciona uma experiência mais fluida aos usuários */

  h1 {
    @apply mb-8 font-bold leading-tight;
    font-size: clamp(2.25rem, 5vw + 1rem, 3.5rem);
  }

  h2 {
    @apply mb-6 font-bold leading-tight;
    font-size: clamp(1.875rem, 4vw + 0.5rem, 2.75rem);
  }

  h3 {
    @apply mb-4 font-bold;
    font-size: clamp(1.5rem, 3vw + 0.5rem, 2.25rem);
  }

  h4 {
    @apply mb-4 font-bold;
    font-size: clamp(1.25rem, 2vw + 0.5rem, 1.75rem);
  }

  h5 {
    @apply mb-4 font-bold;
    font-size: clamp(1.125rem, 1.5vw + 0.5rem, 1.5rem);
  }

  h6 {
    @apply mb-4 font-bold;
    font-size: clamp(1rem, 1vw + 0.5rem, 1.25rem);
  }


  /*
   |-----------------------------------------------------------------------------
   | PARÁGRAFOS (P) E TEXTO EM GERAL
   |-----------------------------------------------------------------------------
   | Damos um espaçamento (margin-bottom) e um tamanho de fonte confortável.
   | sm:text-xl faz com que, em telas no breakpoint "sm" (ex: >= 640px), 
   | o texto fique maior.
   */
  /* p {
    @apply mb-6 text-lg leading-relaxed sm:text-xl;
  } */

  p {
    @apply mb-6 leading-relaxed;
    font-size: clamp(1rem, 1.5vw + 0.5rem, 1.25rem);
  }

  /*
   |-----------------------------------------------------------------------------
   | LINKS (A)
   |-----------------------------------------------------------------------------
   | Usamos a cor de destaque (var(--accent)) e sublinhado. 
   | O :hover diminui a opacidade para dar um efeito de feedback ao usuário.
   */
  a {
    color: var(--accent);
    @apply hover:underline hover:opacity-80;
  }

  /*
   |-----------------------------------------------------------------------------
   | TEXTO EM NEGRITO (STRONG) E ITÁLICO (EM)
   |-----------------------------------------------------------------------------
   | Mantemos a semântica e a ênfase visualmente clara.
   */
  strong {
    @apply font-bold;
  }

  em {
    @apply italic;
  }

  /*
   |-----------------------------------------------------------------------------
   | LISTAS (UL, OL)
   |-----------------------------------------------------------------------------
   | Definimos margens, padding à esquerda e estilos de lista (disc, decimal).
   */
  ul,
  ol {
    @apply mb-6 pl-8;
  }

  /* li {
    @apply mb-2 text-lg sm:text-xl;
  } */

  li {
    @apply mb-2;
    font-size: clamp(1rem, 1.5vw + 0.5rem, 1.25rem);
  }

  ul > li {
    @apply list-disc;
  }

  ol > li {
    @apply list-decimal;
  }

  /*
   |-----------------------------------------------------------------------------
   | BLOCOS DE CÓDIGO (PRE, CODE)
   |-----------------------------------------------------------------------------
   | Usados para exibir trechos de código de forma destacada. 
   | O overflow-x-auto faz com que apareça scroll horizontal em códigos longos.
   */
  pre {
    @apply mb-6 overflow-x-auto rounded-lg p-4;
    background-color: var(--background-code);
  }

  code {
    @apply rounded px-1 font-mono text-sm;
    background-color: var(--background-code);
  }

  /*
   |-----------------------------------------------------------------------------
   | BLOCKQUOTE (CITAÇÕES)
   |-----------------------------------------------------------------------------
   | Recuo (padding-left), texto em itálico e borda esquerda na cor de destaque.
   */
  blockquote {
    @apply mb-6 pl-4 italic;
    border-left: 4px solid var(--accent);
  }

  /*
   |-----------------------------------------------------------------------------
   | TABELAS (TABLE, TH, TD)
   |-----------------------------------------------------------------------------
   | Tabelas ocupando toda a largura (w-full) e sem espaços entre as células
   | (border-collapse). Também definimos bordas para separar conteúdo.
   */
  table {
    @apply mb-6 w-full border-collapse;
  }

  th {
    @apply p-2 text-left font-bold;
    border-bottom: 2px solid var(--border);
  }

  td {
    @apply p-2;
    border-bottom: 1px solid var(--border);
  }

  /*
   |-----------------------------------------------------------------------------
   | FORMULÁRIOS (INPUT, TEXTAREA, SELECT)
   |-----------------------------------------------------------------------------
   | Unificamos a estilização para vários tipos de input:
   | - Todos terão largura cheia (w-full), padding, bordas arredondadas e 
   |   cor de fundo conforme o tema.
   | - A borda usa nossa variável de cor de borda.
   */
  input[type="email"],
  input[type="password"],
  input[type="search"],
  input[type="text"],
  input[type="url"],
  input[type="number"],
  input[type="date"],
  input[type="datetime-local"],
  input[type="month"],
  input[type="week"],
  input[type="time"],
  input[type="tel"],
  select[multiple],
  input,
  textarea,
  select {
    @apply w-full rounded-lg p-2;
    background-color: var(--background);
    border: 1px solid var(--form-border);
  }

  /*
   |-----------------------------------------------------------------------------
   | ESTADO DE FOCUS EM FORMULÁRIOS
   |-----------------------------------------------------------------------------
   | outline-none remove as bordas padrão do navegador,
   | e adicionamos um box-shadow para indicar que está em foco.
   */
   input[type="email"]:focus,
   input[type="password"]:focus,
   input[type="search"]:focus,
   input[type="text"]:focus,
   input[type="url"]:focus,
   input[type="number"]:focus,
   input[type="date"]:focus,
   input[type="datetime-local"]:focus,
   input[type="month"]:focus,
   input[type="week"]:focus,
   input[type="time"]:focus,
   input[type="tel"]:focus,
   select[multiple]:focus,
   input:focus,
   textarea:focus,
   select:focus {
    @apply outline-none;
    box-shadow: 0 0 0 2px var(--focus-ring);
  }

  /*
   |-----------------------------------------------------------------------------
   | BOTÕES (BUTTON, INPUT[TYPE=BUTTON/SUBMIT/RESET/FILE])
   |-----------------------------------------------------------------------------
   | Usamos a cor de destaque (accent) para o fundo e o texto fica branco.
   | Adicionamos hover e transition para ficar mais agradável ao usuário.
   */
  input[type="button"],
  input[type="submit"],
  input[type="reset"],
  ::file-selector-button,
  button {
    @apply rounded-lg px-4 py-2 text-white transition-opacity hover:opacity-90;
    background-color: var(--accent);
  }

  /*
   |-----------------------------------------------------------------------------
   | IMAGENS E MÍDIA (IMG, VIDEO, AUDIO)
   |-----------------------------------------------------------------------------
   | As imagens terão largura máxima de 100% (max-w-full) e altura automática 
   | (h-auto) para ficarem responsivas, centralizadas (ms-auto), além de bordas arredondadas (rounded-lg).
   */
  img {
    @apply mx-auto mb-6 h-auto max-w-full rounded-lg hover:scale-[1.02];
  }

  /* Figcaption (legendas de imagem) */
  figcaption {
    @apply mt-2 text-sm italic;
    text-align: center;
  }

  video,
  audio {
    @apply mb-6 w-full;
  }

  /*
   |-----------------------------------------------------------------------------
   | DIVISORES (HR)
   |-----------------------------------------------------------------------------
   | Linha horizontal para separar seções de conteúdo.
   | Usamos var(--border) para a cor da borda, de acordo com o tema.
   */
  hr {
    @apply my-8;
    border-top: 1px solid var(--border);
  }
}

/* 
 |-----------------------------------------------------------------------------
 | UTILITÁRIOS PERSONALIZADOS
 |-----------------------------------------------------------------------------
 | A camada utilities (layer utilities) é onde definimos classes utilitárias 
 | adicionais, caso as classes do Tailwind não cubram nossas necessidades.
 */
@layer utilities {
  /*
   |-----------------------------------------------------------------------------
   | CONTENT-WRAPPER
   |-----------------------------------------------------------------------------
   | Caso queiramos reaproveitar o max-w-4xl e o mx-auto + px-4 em um 
   | container específico.
   */
  .content-wrapper {
    @apply mx-auto max-w-4xl px-4;
  }

  /*
   |-----------------------------------------------------------------------------
   | TEXT-BALANCE
   |-----------------------------------------------------------------------------
   | Propriedade moderna (text-wrap: balance) que melhora a distribuição 
   | de palavras, mas não é suportada em todos os navegadores.
   | Lembre de testar compatibilidade.
   */
  .text-balance {
    text-wrap: balance;
  }

  /*
   |-----------------------------------------------------------------------------
   | PROSE
   |-----------------------------------------------------------------------------
   | Caso queira um estilo de texto ao estilo "prose" do Tailwind, mas 
   | sem o limite padrão de largura.
   */
  .prose {
    @apply max-w-none;
  }

  /*
   |-----------------------------------------------------------------------------
   | PROSE > IMG
   |-----------------------------------------------------------------------------
   | Exemplo de como centralizar imagens dentro de um container .prose.
   */
  .prose img {
    @apply mx-auto;
  }
}

/* 
---

 */

Enter fullscreen mode Exit fullscreen mode

Insira o conteúdo do 2º arquivo Tailwind customizado em custom2.css

Exibir mais …
Crie o arquivo app/assets/stylesheets/custom_tailwind/custom2.css e copie o conteúdo a seguir.
/* =================================================================
   CONFIGURAÇÃO DE VARIÁVEIS CSS
   Definição centralizada de todas as variáveis do projeto
   ================================================================= */
:root {
  /* Cores - Tema Claro */
  --color-primary: #2563eb; /* blue-600 do Tailwind */
  --color-primary-hover: #1d4ed8; /* blue-700 do Tailwind */
  --color-background: #ffffff;
  --color-text: #1f2937; /* gray-800 do Tailwind */
  --color-text-muted: #4b5563; /* gray-600 do Tailwind */
  --color-border: #d1d5db; /* gray-300 do Tailwind */
  --color-input-bg: #f9fafb; /* gray-50 do Tailwind */
  --color-code-bg: #f3f4f6; /* gray-100 do Tailwind */
  --color-code-text: #273e65; /* blue-800 do Tailwind */

  /* Espaçamento */
  --spacing-base: 1rem;
  --spacing-lg: 1.5rem;
  --spacing-xl: 2rem;

  /* Border Radius */
  --radius-base: 0.375rem;
  --radius-lg: 0.5rem;

  /* Larguras Máximas */
  --max-width-content: 48rem; /* 768px */
}

/* Configuração do tema escuro usando prefers-color-scheme */
@media (prefers-color-scheme: dark) {
  :root {
    /* Cores - Tema Escuro */
    --color-primary: #0284c7; /* sky-600 do Tailwind */
    --color-primary-hover: #6990c7; /* blue-400 do Tailwind */
    --color-background: #111827; /* gray-900 do Tailwind */
    --color-text: #f3f4f6; /* gray-100 do Tailwind */
    --color-text-muted: #9ca3af; /* gray-400 do Tailwind */
    --color-border: #374151; /* gray-700 do Tailwind */
    --color-input-bg: #1f2937; /* gray-800 do Tailwind */
    --color-code-bg: #1f2937; /* gray-800 do Tailwind */
    --color-code-text: #e8ecf6; /* blue-100 do Tailwind */
  }
}

/* Importações do Tailwind */
@tailwind base;
@tailwind components;
@tailwind utilities;

/* =================================================================
     ESTILOS BASE
     Configurações globais e reset de elementos HTML
     ================================================================= */
@layer base {
  body {
    @apply mx-auto max-w-3xl px-4 leading-relaxed tracking-wide sm:px-6 md:px-8 lg:px-12;
    background-color: var(--color-background);
    color: var(--color-text);
    font-family:
      system-ui,
      -apple-system,
      BlinkMacSystemFont,
      "Segoe UI",
      Roboto,
      "Helvetica Neue",
      Arial,
      sans-serif;
      line-height: clamp(1.5, 2vw + 1.2, 1.75);
  }

  /* Links */
  a {
    color: var(--color-primary);
    @apply hover:underline;
  }

  /* Títulos - Usando variáveis CSS para tamanhos consistentes */
  h1,
  h2,
  h3,
  h4,
  h5,
  h6 {
    margin-top: var(--spacing-lg);
    margin-bottom: var(--spacing-base);
    @apply font-bold leading-tight;
  }

  /* Sistema de escala tipográfica responsiva */
  /* h1 {
    @apply text-3xl sm:text-4xl md:text-5xl;
  }
  h2 {
    @apply text-2xl sm:text-3xl md:text-4xl;
  }
  h3 {
    @apply text-xl sm:text-2xl md:text-3xl;
  }
  h4 {
    @apply text-lg sm:text-xl md:text-2xl;
  }
  h5 {
    @apply text-base sm:text-lg md:text-xl;
  }
  h6 {
    @apply text-sm sm:text-base md:text-lg;
  } */

  /* Sistema de escala tipográfica fluida usando clamp() */
  h1 {
    font-size: clamp(2rem, 5vw + 1rem, 3.5rem);
  }

  h2 {
    font-size: clamp(1.75rem, 4vw + 0.75rem, 3rem);
  }

  h3 {
    font-size: clamp(1.5rem, 3vw + 0.75rem, 2.5rem);
  }

  h4 {
    font-size: clamp(1.25rem, 2vw + 0.75rem, 2rem);
  }

  h5 {
    font-size: clamp(1.1rem, 1.5vw + 0.75rem, 1.5rem);
  }

  h6 {
    font-size: clamp(1rem, 1vw + 0.75rem, 1.25rem);
  }

  /* Elementos de texto */
  /* p {
    margin-bottom: var(--spacing-base);
    @apply text-lg leading-relaxed;
  } */

  /* Parágrafos com tipografia fluida */
  p {
    margin-bottom: var(--spacing-base);
    font-size: clamp(1rem, 1.5vw + 0.75rem, 1.25rem);
    line-height: clamp(1.6, 2vw + 1.2, 1.8);
  }

  /* Listas */
  ul,
  ol {
    margin-bottom: var(--spacing-base);
    padding-left: var(--spacing-lg);
  }
  ul {
    @apply list-disc;
  }
  ol {
    @apply list-decimal;
  }
  /* li {
    margin-bottom: calc(var(--spacing-base) * 0.5);
  } */
  /* Elementos de lista com tipografia fluida */
  li {
    margin-bottom: calc(var(--spacing-base) * 0.5);
    font-size: clamp(1rem, 1.5vw + 0.75rem, 1.25rem);
  }


  /* Imagens responsivas */
  img {
    @apply h-auto w-full;
    border-radius: var(--radius-lg);
    margin: var(--spacing-base) 0;
  }

  /* Citações */
  /* blockquote {
    border-left: 4px solid var(--color-border);
    color: var(--color-text-muted);
    padding-left: var(--spacing-base);
    @apply my-4 italic;
  } */

  /* Código inline e blocos de código */
  /* code {
    background-color: var(--color-code-bg);
    color: var(--color-code-text);
    border-radius: var(--radius-base);
    @apply px-1 py-0.5 text-sm;
  } */

  /* pre {
    background-color: var(--color-code-bg);
    border-radius: var(--radius-lg);
    padding: var(--spacing-base);
    @apply overflow-x-auto text-sm;
  } */

  /* Citações com tipografia fluida */
  blockquote {
    border-left: 4px solid var(--color-border);
    color: var(--color-text-muted);
    padding-left: var(--spacing-base);
    @apply my-4 italic;
    font-size: clamp(1rem, 1.5vw + 0.75rem, 1.25rem);
  }

  /* Código inline com tipografia fluida */
  code {
    background-color: var(--color-code-bg);
    color: var(--color-code-text);
    border-radius: var(--radius-base);
    @apply px-1 py-0.5;
    font-size: clamp(0.875rem, 1vw + 0.5rem, 1rem);
  }

  /* Blocos de código com tipografia fluida */
  pre {
    background-color: var(--color-code-bg);
    border-radius: var(--radius-lg);
    padding: var(--spacing-base);
    @apply overflow-x-auto;
    font-size: clamp(0.875rem, 1vw + 0.5rem, 1rem);
  }

  /* Tabelas */
  table {
    @apply my-4 w-full border-collapse;
  }

  /* th,
  td {
    border-bottom: 1px solid var(--color-border);
    padding: calc(var(--spacing-base) * 0.5) var(--spacing-base);
    @apply text-left;
  } */

  /* Células de tabela com tipografia fluida */
  th, td {
    border-bottom: 1px solid var(--color-border);
    padding: calc(var(--spacing-base) * 0.5) var(--spacing-base);
    @apply text-left;
    font-size: clamp(0.875rem, 1vw + 0.5rem, 1rem);
  }

  th {
    background-color: var(--color-code-bg);
    @apply font-semibold;
  }

  /* Formulários */
  input[type="email"],
  input[type="password"],
  input[type="search"],
  input[type="text"],
  input[type="url"],
  input[type="number"],
  input[type="date"],
  input[type="datetime-local"],
  input[type="month"],
  input[type="week"],
  input[type="time"],
  input[type="tel"],
  select[multiple],
  input,
  textarea,
  select,
  button {
    border-radius: var(--radius-base);
    margin-bottom: var(--spacing-base);
    padding: calc(var(--spacing-base) * 0.5) var(--spacing-base);
    width: 100%;
    border: 1px solid var(--color-border);
  }

  ::file-selector-button {
    border-radius: var(--radius-base);
    margin-bottom: var(--spacing-base);
    padding: calc(var(--spacing-base) * 0.5) var(--spacing-base);
    border: 1px solid var(--color-border);
  }

  /* Campos de formulário */
  input[type="email"],
  input[type="password"],
  input[type="search"],
  input[type="text"],
  input[type="url"],
  input[type="number"],
  input[type="date"],
  input[type="datetime-local"],
  input[type="month"],
  input[type="week"],
  input[type="time"],
  input[type="tel"],
  select[multiple],
  input,
  textarea,
  select {
    background-color: var(--color-input-bg);
    color: var(--color-text);
  }

  /* Estados de foco */
  input[type="email"]:focus,
  input[type="password"]:focus,
  input[type="search"]:focus,
  input[type="text"]:focus,
  input[type="url"]:focus,
  input[type="number"]:focus,
  input[type="date"]:focus,
  input[type="datetime-local"]:focus,
  input[type="month"]:focus,
  input[type="week"]:focus,
  input[type="time"]:focus,
  input[type="tel"]:focus,
  select[multiple]:focus,
  input:focus,
  textarea:focus,
  select:focus {
    @apply outline-none ring-2 ring-blue-500 focus:ring-offset-2;
  }

  /* Botões */
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"],
  ::file-selector-button {
    background-color: var(--color-primary);
    color: white;
    @apply cursor-pointer hover:bg-blue-700;
  }
}

/* =================================================================
     COMPONENTES
     Classes reutilizáveis para padrões comuns de design
     ================================================================= */
@layer components {
  .container-page {
    margin: 0 auto;
    max-width: var(--max-width-content);
    padding: 0 var(--spacing-base);
    @apply sm:px-6 md:px-8 lg:px-12;
  }

  .table-striped tbody tr:nth-of-type(odd) {
    background-color: var(--color-input-bg);
  }

  .code-block {
    background-color: var(--color-code-bg);
    border-radius: var(--radius-lg);
    padding: var(--spacing-base);
    @apply overflow-x-auto text-sm;
  }
}

/* =================================================================
     UTILITÁRIOS
     Classes utilitárias personalizadas
     ================================================================= */
@layer utilities {
  .text-primary {
    color: var(--color-primary);
  }

  .bg-primary {
    background-color: var(--color-primary);
  }
}

Enter fullscreen mode Exit fullscreen mode

Insira o conteúdo do 3º arquivo Tailwind customizado em custom3.css

Exibir mais …
Crie o arquivo app/assets/stylesheets/custom_tailwind/custom3.css e copie o conteúdo a seguir.
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  body {
    @apply min-h-screen bg-gradient-to-t from-slate-50 to-slate-200 text-slate-950;
  }

  h1 {
    @apply px-4 py-8 text-2xl;
  }

  h2 {
    @apply px-4 py-4 text-xl;
  }

  h3 {
    @apply px-4 pb-1 pt-2 text-xl;
  }

  h4,
  h5,
  h6 {
    @apply px-4 pb-0 pt-1 text-lg;
  }

  a {
    @apply underline decoration-sky-800 underline-offset-2;
  }

  a:hover {
    @apply decoration-2;
  }

  header,
  main,
  footer {
    @apply container max-w-3xl;
  }

  header {
    @apply mt-4 rounded-t-lg border border-slate-300 bg-slate-50;

    h1 {
      @apply pb-1 text-slate-900;
    }

    h2 {
      @apply font-normal text-slate-700;
    }

    p {
      @apply px-4 py-4 pt-0 text-base font-normal text-slate-500;
    }
  }

  main {
    @apply border-l border-r border-slate-300 bg-white;

    article {
      @apply py-2;

      p {
        @apply p-4 px-4 text-justify text-base leading-normal;

        img {
          @apply float-start m-3 rounded border border-gray-300 p-1;
        }
      }

      blockquote {
        @apply mx-4 p-4 px-4 text-justify text-base leading-normal text-slate-500;
        @apply border-l-4 border-l-slate-500;
      }

      ul {
        @apply m-4 rounded border border-gray-100;
        @apply divide-y divide-gray-200;

        li {
          @apply p-4;

          p {
            @apply my-0;
          }
        }
      }

      span {
        @apply text-base leading-normal;
      }

      button[type="button"] {
        @apply my-2 ml-4 rounded bg-amber-200 px-2 py-1;

        &:hover {
          @apply transition hover:bg-amber-500;
        }
      }
    }

    form {
      @apply p-4;

      fieldset {
        @apply rounded border border-gray-300 p-4;
      }

      input,
      textarea,
      select,
      button {
        @apply my-2 rounded border border-gray-300 p-2 shadow-sm;
        @apply ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-slate-800;
        &:disabled {
          @apply border-gray-100 ring-gray-200 focus:ring-gray-500;

          &:checked {
            @apply bg-gray-200;
          }
        }
      }
      input:disabled + label {
        @apply text-gray-500;
      }
      select {
        @apply w-96 rounded-md border-0 py-1.5;
      }

      button {
        @apply transition hover:bg-slate-200;
      }
    }
  }

  footer {
    @apply mb-4 rounded-b-lg border border-slate-300 bg-slate-50 p-4;
  }

  aside {
    @apply absolute right-0 top-0 m-4 max-h-[90vh] w-96 overflow-auto p-2;
    @apply rounded-md border border-slate-300 bg-white shadow;

    &[id="output"] {
      @apply left-0;
      code {
        @apply max-h-[90vh] overflow-y-scroll leading-4;
      }
    }

    nav {
      @apply top-0 flex justify-between bg-white;

      a {
        @apply rounded px-2 py-1 no-underline;

        &:hover {
          @apply transition hover:bg-slate-200;
        }

        &[rel="prev"]:before {
          content: "←";
          @apply mr-0.5;
        }

        &[rel="index"]:before {
          content: "§";
          @apply mr-0.5;
        }

        &[href="#"]:after {
          content: "↻";
          @apply ml-0.5;
        }

        &[rel="next"]:after {
          content: "→";
          @apply ml-0.5;
        }
      }
    }

    div {
      @apply max-h-[80vh] overflow-y-scroll;
    }

    hr {
      @apply mx-1 my-2;
    }

    p {
      @apply p-2 text-base;
    }

    code {
      @apply mt-2 block whitespace-pre-wrap px-2 py-1 leading-normal;
    }

    input {
      @apply m-0.5 p-1 font-mono text-base;
    }

    label {
      @apply m-0.5 px-0.5 font-mono text-base font-bold;
    }

    button[type="button"] {
      @apply my-1 min-w-24 rounded bg-slate-100 px-2 py-1;

      &:hover {
        @apply transition hover:bg-slate-200;
      }
    }
  }
}

@layer components {
  code {
    @apply relative inline-block rounded bg-slate-50 px-1 py-0.5 font-mono;

    span {
      @apply text-green-600;
    }

    em {
      @apply text-gray-500;
    }

    i {
      @apply not-italic text-red-800;
    }

    &[contenteditable] {
      @apply border border-green-100 bg-green-50;
    }

    &[contenteditable]::after {
      content: "✎";

      @apply absolute right-0 top-0 m-1 inline-flex h-6 w-6 items-center justify-center;
      @apply rounded-full bg-amber-100 text-sm font-semibold text-black;
    }
  }

  .accordion {
    article {
      @apply p-0;
      h3 {
        @apply border-b border-slate-300 bg-slate-100;
        @apply cursor-pointer;
        background-image: url(../images/arrows.png);
        background-position: 98% 12px;
        background-repeat: no-repeat;

        &:hover {
          @apply bg-slate-200;
        }
      }
      p {
        @apply hidden border-b border-slate-300;
      }
    }
  }

  .events {
    @apply list-none p-0;
    li {
      @apply p-[2px];
    }

    li span {
      @apply mx-1 mr-2 inline-block min-w-6 rounded-full bg-amber-400 px-1 text-center text-white;
    }
  }
}

@layer utilities {
  .formatter {
    h1,
    h2,
    h3 {
      @apply px-4 pb-1 pt-2 text-lg text-slate-900;
    }

    h1 {
      &::before {
        content: "<h1>";
        @apply mx-2 text-base text-sky-700;
      }

      &::after {
        content: "</h1>";
        @apply mx-2 text-base text-sky-700;
      }
    }
    h2 {
      &::before {
        content: "<h2>";
        @apply mx-2 text-base text-sky-700;
      }

      &::after {
        content: "</h2>";
        @apply mx-2 text-base text-sky-700;
      }
    }
    h3 {
      &::before {
        content: "<h3>";
        @apply mx-1 ml-2 text-base text-sky-700;
      }

      &::after {
        content: "</h3>";
        @apply mx-1 text-base text-sky-700;
      }
    }
    p {
      @apply ml-2;
      &::before {
        content: "<p>";
        @apply mr-1 text-base text-sky-700;
      }

      &::after {
        content: "</p>";
        @apply ml-1 text-base text-sky-700;
      }
    }
    div {
      @apply ml-6;
      &::before {
        content: "<div>";
        @apply ml-1 pl-2;
        @apply border-l-4 border-l-amber-500;
        @apply mr-1 block text-base text-sky-700;
      }

      &::after {
        content: "</div>";
        @apply ml-1 pl-2;
        @apply border-l-4 border-l-amber-500;
        @apply ml-1 block text-base text-sky-700;
      }
    }
    li {
      @apply ml-8;
      &::before {
        content: "<li>";
        @apply mr-1 text-base text-sky-700;
      }

      &::after {
        content: "</li>";
        @apply ml-1 text-base text-sky-700;
      }
    }

    header,
    main,
    article,
    article ul,
    div,
    footer {
      background-image: url("data:image/svg+xml,%3Csvg width='10' height='10' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10'%3E%3Cellipse style='fill: rgb(245, 158, 11);' cx='6' cy='1' rx='1' ry='1'/%3E%3C/svg%3E");
      background-position: left;
      background-repeat: repeat-y;
    }

    header {
      &::before {
        content: "<header>";
        @apply ml-1 pl-2;
        @apply border-l-4 border-l-amber-500;
        @apply text-base text-sky-700;
      }

      &::after {
        content: "</header>";
        @apply ml-1 pl-2;
        @apply border-l-4 border-l-amber-500;
        @apply text-base text-sky-700;
      }
    }

    main {
      &::before {
        content: "<main>";
        @apply ml-1 pl-2;
        @apply border-l-4 border-l-amber-500;
        @apply text-base text-sky-700;
      }

      &::after {
        content: "</main>";
        @apply ml-1 pl-2;
        @apply border-l-4 border-l-amber-500;
        @apply text-base text-sky-700;
      }
    }
    article {
      @apply ml-6 mt-4 py-0;
      &::before {
        content: "<article>";
        @apply ml-1 block pl-2;
        @apply border-l-4 border-l-amber-500;
        @apply text-base text-sky-700;
      }

      &::after {
        content: "</article>";
        @apply ml-1 block pl-2;
        @apply border-l-4 border-l-amber-500;
        @apply text-base text-sky-700;
      }
      span {
        &::before {
          content: "<span>";
          @apply mr-1 inline-block text-base text-sky-700;
        }

        &::after {
          content: "</span>";
          @apply ml-1 inline-block text-base text-sky-700;
        }
      }
      i {
        @apply not-italic;
        &::before {
          content: "<i>";
          @apply mr-1 inline-block text-base text-sky-700;
        }

        &::after {
          content: "</i>";
          @apply ml-1 inline-block text-base text-sky-700;
        }
      }
    }

    ul {
      @apply ml-7 mt-4 py-0;
      &::before {
        content: "<ul>";
        @apply ml-1 pl-2;
        @apply border-l-4 border-l-amber-500;
        @apply text-base text-sky-700;
      }

      &::after {
        content: "</ul>";
        @apply ml-1 pl-2;
        @apply border-l-4 border-l-amber-500;
        @apply text-base text-sky-700;
      }
    }

    footer {
      &::before {
        content: "<footer>";
        @apply ml-1 block pl-2;
        @apply border-l-4 border-l-amber-500;
        @apply text-base text-sky-700;
      }

      &::after {
        content: "</footer>";
        @apply ml-1 block pl-2;
        @apply border-l-4 border-l-amber-500;
        @apply text-base text-sky-700;
      }
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

Remova as classes Tailwind do arquivo app/views/layouts/application.html.erb

Exibir mais …
No arquivo application.html.erb, remova ou comente a tag <main>, que está antes e depois da tag <%= yield %> para que não altere o comportamento da estilização customizada que criamos para o Tailwind.
    <%# <main class="container mx-auto mt-28 px-5 flex"> %>
      <%= yield %>
    <%# </main> %>
Enter fullscreen mode Exit fullscreen mode

Alguns passos adicionais para fazer o estilo dos arquivos Tailwind customizados funcionarem.

Exibir mais …

Se você seguiu os passos anteriores, o arquivo app/assets/stylesheets/application.tailwind.css deve ter somente a linha @import "./custom_tailwind/custom1.css"; descomentada.

Deve existir apenas um estilo descomentado. Para testar um outro estilo, primeiro comente o estilo atualmente em uso e descomente o outro estilo que deseja testar.

Após escolher um dos estilos customizados disponíveis, execute o comando abaixo:

$ bin/rails assets:precompile
$ bin/dev 
Enter fullscreen mode Exit fullscreen mode

Caso o comando anterior não funcione para estilizar os elementos HTML, tente primeiro limpar os arquivos anteriores e depois precompilar novamente:

$ bin/rails assets:clobber 
$ bin/rails assets:precompile
$ bin/dev 
Enter fullscreen mode Exit fullscreen mode

Agora sim, um HTML com estilo 🤩

Após configurar o Tailwind com as customizações acima e iniciar o servior do Rails você verá seu HTML estilizado.

Modo dark

Alguns estilos possuem a opção para modo escuro (dark mode). Para confirmar, altere o tema do seu computador nas opções de personalização de cores. Procure no Windows por Ativar modo escuro para apps e alterne entre modo escuro ou claro. A página HTML deverá automaticamente muda após a alteração no sistema operacional, indicando que possui suporte para o modo light e dark.

Passos seguintes

[x] Organizar os estilos de acordo com sua preferência;
[x] Usar estilização a partir de arquivos CSS do projeto, sem usar CDN;
[x] Replicar a capacidade de um framework classless CSS usando Tailwind;
[-] Atualizar dinamicamente no navegador as alterações feitas no projeto usando Rails Live Reload;
[-] Se quiser gastar um pouco mais de tempo com o frontend, verifique as opções de customização do seu estilo favorito;

Referências

Top comments (0)