DEV Community

Dailson Igo Araujo Palheta
Dailson Igo Araujo Palheta

Posted 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 estilizando usando Tailwind como um framework classless 🤩

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.

Repositório

Acesse aqui o repositório com o código do tutorial

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

Image of AssemblyAI tool

Challenge Submission: SpeechCraft - AI-Powered Speech Analysis for Better Communication

SpeechCraft is an advanced real-time speech analytics platform that transforms spoken words into actionable insights. Using cutting-edge AI technology from AssemblyAI, it provides instant transcription while analyzing multiple dimensions of speech performance.

Read full post

Top comments (0)

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

👋 Kindness is contagious

Engage with a sea of insights in this enlightening article, highly esteemed within the encouraging DEV Community. Programmers of every skill level are invited to participate and enrich our shared knowledge.

A simple "thank you" can uplift someone's spirits. Express your appreciation in the comments section!

On DEV, sharing knowledge smooths our journey and strengthens our community bonds. Found this useful? A brief thank you to the author can mean a lot.

Okay