Skip to content

Styling & Theming ​

Tailwind CSS patterns and responsive design conventions.

Tailwind CSS ​

The project uses Tailwind CSS v4 with a custom design system.

For general Tailwind usage, see the official Tailwind documentation.

Container Queries: The Default ​

Critical: Use Container Queries, Not Viewport Queries

Always use container queries (@sm, @md, @lg, etc.) instead of viewport queries (sm, md, lg) for responsive design.

The main content area already has @container/main set up, so all your components respond to the available space, not the viewport size.

Why Container Queries? ​

Your components need to work in:

  • Full-width pages
  • Sidebars
  • Dialogs
  • Cards
  • Split layouts

Container queries make components responsive to their container size, not the viewport, so they adapt correctly everywhere.

The Main Content Area ​

The app shell already sets up the container:

vue
<!-- AppShell.vue - Already configured -->
<main class="... @container/main">
  <slot />
</main>

This means your pages automatically get container query support.

Using Container Queries ​

vue
<template>
  <!-- âś… Use container queries (responds to parent size) -->
  <div class="
    flex flex-col
    @md:flex-row
    @lg:gap-6
    @xl:grid
    @xl:grid-cols-3
  ">
    <div>Content</div>
  </div>
  
  <!-- ❌ Don't use viewport queries (responds to screen size) -->
  <div class="
    flex flex-col
    md:flex-row
    lg:gap-6
  ">
    <div>Content</div>
  </div>
</template>

Container Query Breakpoints ​

BreakpointMin WidthUsage
@sm384pxSmall containers
@md448pxMedium containers
@lg512pxLarge containers
@xl576pxExtra large
@2xl672px2X large
@3xl768px3X large (common)
@4xl896px4X large
@5xl1024px5X large

Common Pattern

Most components switch to multi-column at @3xl (768px container width).

When to Use Viewport Queries ​

Only use viewport queries (sm, md, lg) when you specifically need to respond to screen size, not container size:

vue
<template>
  <!-- Show/hide based on screen size -->
  <div class="block lg:hidden">
    Mobile navigation
  </div>
  
  <!-- Different component for mobile vs desktop -->
  <UiSheet v-if="isMobile">Mobile</UiSheet>
  <UiDialog v-else>Desktop</UiDialog>
</template>

<script setup>
import { useMediaQuery } from '@vueuse/core'
const isMobile = useMediaQuery('(max-width: 768px)')
</script>

Design Tokens ​

Use semantic color tokens:

vue
<template>
  <!-- âś… Use semantic tokens -->
  <div class="bg-card text-card-foreground border-border">
    <h1 class="text-heading-foreground">Title</h1>
    <p class="text-muted-foreground">Description</p>
  </div>
  
  <!-- ❌ Don't use hardcoded colors -->
  <div class="bg-white text-black border-gray-200">
    <h1 class="text-gray-900">Title</h1>
  </div>
</template>

Common Tokens:

  • bg-background / text-foreground - Page colors
  • bg-card / text-card-foreground - Card colors
  • bg-primary / text-primary-foreground - Brand colors
  • text-muted-foreground - Secondary text
  • border-border - Borders
  • text-destructive - Errors

Dark Mode

Tokens automatically adapt to dark mode!

RTL Support ​

Always use logical properties for RTL support:

vue
<template>
  <!-- âś… RTL-aware -->
  <div class="ms-4 me-2 ps-6 pe-4 text-start">
    Content
  </div>
  
  <!-- ❌ Not RTL-aware -->
  <div class="ml-4 mr-2 pl-6 pr-4 text-left">
    Content
  </div>
</template>

Logical Properties:

  • ms-* / me-* - Margin start/end (not left/right)
  • ps-* / pe-* - Padding start/end
  • start-* / end-* - Position start/end
  • text-start / text-end - Text alignment
  • border-s-* / border-e-* - Borders

See RTL Support → for complete guide.

Component Styling ​

Use the cn() utility for conditional classes:

vue
<script setup>
import { cn } from '@/lib/utils'

const buttonClass = computed(() => cn(
  'px-4 py-2 rounded-md',
  props.active && 'bg-primary',
  props.disabled && 'opacity-50'
))
</script>

See UI Components → for component patterns.

Best Practices ​

Do's âś…

  • Use container queries (@md) as default
  • Use semantic color tokens
  • Use logical properties (ms-, ps-, text-start)
  • Use cn() for conditional classes
  • See Tailwind docs for utilities

Don'ts ❌

  • Don't use viewport queries (md) unless specifically needed
  • Don't use hardcoded colors
  • Don't use ml/mr/left/right (breaks RTL)
  • Don't forget mobile-first approach

Quick Reference ​

vue
<!-- Container query responsive grid -->
<div class="grid grid-cols-1 @md:grid-cols-2 @lg:grid-cols-3 gap-4">
  <div>Item</div>
</div>

<!-- RTL-aware spacing -->
<div class="ms-4 ps-6 text-start">Content</div>

<!-- Semantic colors -->
<div class="bg-card text-card-foreground border-border">Card</div>

<!-- Conditional classes -->
<div :class="cn('base-class', condition && 'active-class')">
  Content
</div>

Related: UI Components → | RTL Support → | Data Tables → | Tailwind Docs →