Documentation

Spacing & Layout

VitNode's spacing and layout rules - the 4px Tailwind spacing scale, gap over space-*, grouping by proximity, container queries, and RTL-safe logical properties, shown live.

Every gap, padding and margin in VitNode comes from one scale. Pick a step, never a pixel, and layouts line up on their own.

Spacing scale

1 unit = --spacing = …

  • 1
    …px
  • 2
    …px
  • 3
    …px
  • 4
    …px
  • 6
    …px
  • 8
    …px
  • 12
    …px
  • 16
    …px
  • 24
    …px

One unit is 0.25rem (4px), so p-4 is 16px and gap-6 is 24px. Stay on the scale: p-4, never p-[16px].

Gap, not space-*

flex-wrap gap-2Do
  • react
  • hono
  • drizzle
  • tailwind
  • postgres
  • vite
flex-wrap space-x-2Don't
  • react
  • hono
  • drizzle
  • tailwind
  • postgres
  • vite

Hit Add tags until the rows wrap. space-x-* puts margins on children, so wrapped rows touch and hidden items leave holes. gap-* belongs to the parent and just works.

<ul className="flex flex-wrap gap-2">{tags}</ul>

Never mix margin or padding with gap on the same element. One job, one tool.

Group by proximity

Do

Profile

Shown next to your posts.

Contact

Where we send the important stuff.

gap-1 → gap-2 → gap-4 → gap-8. Related things huddle, sections breathe.
Don't

Profile

Shown next to your posts.

Contact

Where we send the important stuff.

gap-4 everywhere. Which label belongs to which input? Nobody knows.

Distance is meaning. Things that belong together sit closer than things that don't: gap-1 for a title and its hint, gap-2 for a label and its input, gap-8 between sections.

Cards bring their own padding

size="default"
--card-spacing: …

Header, content and footer share one value.

size="sm"
--card-spacing: …

Header, content and footer share one value.

Card sets --card-spacing once (24px, or 16px with size="sm") and every part reuses it. Don't pad the card again, use CardHeader and CardContent.

Responsive layout

0px · 1 column

  • Posts
  • Members
  • Files
  • Events
  • Polls
  • Badges
Drag me

grid-cols-1 @3xs:grid-cols-2 @md:grid-cols-3

Drag the handle. Container queries (@container + @md:) react to the box a component lives in, so the same widget works in a sidebar and a full page.

Page layouts use viewport breakpoints, mobile first: write the phone layout, then add sm:, md:, lg: on top.

smmdlgxl2xl
640px768px1024px1280px1536px

useIsMobile() flips at md (768px), so CSS and JS agree on what a phone is.

Logical properties for RTL

dir="ltr"
Ada Lovelace
Admin
dir="rtl"
ليلى
مشرف

Switch to pl- / ml- and watch the Arabic row break. ps-, pe-, ms-, me-, start- and border-s follow the reading direction, so one class list serves both.

<div className="flex items-center gap-3 border-s-4 ps-3">
  <ChevronRightIcon className="ms-auto rtl:rotate-180" />
</div>

Layout rules

  • flex and grid with gap-* for layout. No floats, and absolute positioning only for real overlays.
  • Semantic alignment: items-center, justify-between, self-end.
  • Mobile first: the base class is the phone, prefixes add space.
  • max-w-prose (65ch) on long paragraphs, so lines stay easy to follow on a wide monitor.