MinhVo

Minh Vo

rss feed

Slaying code & making it lit fr fr 🔥 tagline

Hey there 👋 I'm an AI Engineer with 7 years of experience building scalable web and mobile applications. Currently at Neurond AI (May 2025 — present), architecting an Enterprise AI Assistant Platform with multi-tenant RAG on pgvector, multi-provider LLM orchestration, and Azure-native infrastructure. Previously spent 5+ years at SNAPTEC (Sep 2019 — Apr 2025), leading SaaS themes, admin dashboards, and e-commerce platforms — earned the Hero of the Year award in 2021. I specialize in TypeScript, React, Next.js, and AI-Native engineering with Claude Code and Cursor.bio

Back to blogs

CSS Logical Properties: Writing-Mode Aware Layouts

Use logical properties: margin-inline, padding-block, and multi-directional layouts.

CSSLogical PropertiesLayoutFrontend

By MinhVo

Introduction

For decades, CSS used physical directions — top, right, bottom, left — to control layout. This worked fine as long as every language on the web read from left to right, top to bottom. But the web is global. Arabic, Hebrew, Persian, and Urdu read right to left. Traditional Chinese and Japanese can be written top to bottom. When your layout is hard-coded with physical properties, supporting these writing modes means rewriting every margin, padding, and positioning rule.

CSS Logical Properties and Values solve this by mapping layout concepts to the flow of the document rather than fixed physical directions. Properties like margin-inline-start, padding-block-end, and inset-inline automatically adapt to the current writing-mode and direction. This means a single stylesheet can serve left-to-right English, right-to-left Arabic, and vertical Japanese without any conditional logic or duplicated rules.

Beyond internationalization, logical properties also produce more semantically meaningful code. When you write margin-block: 2rem, you are expressing intent — "space before and after in the block direction" — rather than prescribing a physical direction that may not hold in all contexts. This guide covers every logical property, maps them to their physical equivalents, and demonstrates real-world patterns for building truly direction-agnostic layouts.

CSS Logical Properties illustration

Understanding Logical Properties: Core Concepts

Writing Modes and the Flow Model

CSS defines two abstract axes for every element:

  • Inline axis: The direction in which text flows (left-to-right in English, right-to-left in Arabic)
  • Block axis: The direction in which blocks stack (top-to-bottom in English, right-to-left in vertical Japanese)

The writing-mode property controls which physical directions these axes map to:

writing-modedirectionInline axisBlock axis
horizontal-tbltrleft → righttop → bottom
horizontal-tbrtlright → lefttop → bottom
vertical-rlltrtop → bottomright → left
vertical-lrltrtop → bottomleft → right

Logical properties reference these abstract axes rather than physical directions. So margin-inline-start means "the margin at the start of the inline axis," which is the left edge in English but the right edge in Arabic.

The Property Mapping

Every physical property has one or more logical equivalents:

Margin:

  • margin-top / margin-bottom → margin-block-start / margin-block-end
  • margin-left / margin-right → margin-inline-start / margin-inline-end
  • Shorthand: margin-block: 1rem 2rem (block-start block-end)
  • Shorthand: margin-inline: auto (inline-start inline-end)

Padding:

  • padding-top / padding-bottom → padding-block-start / padding-block-end
  • padding-left / padding-right → padding-inline-start / padding-inline-end
  • Shorthand: padding-block: 1rem / padding-inline: 2rem

Borders:

  • border-top → border-block-start
  • border-left → border-inline-start
  • Shorthand: border-block-width: 2px / border-inline-style: solid

Sizing:

  • width → inline-size
  • height → block-size
  • min-width → min-inline-size
  • max-height → max-block-size

Positioning:

  • top / bottom → inset-block-start / inset-block-end
  • left / right → inset-inline-start / inset-inline-end
  • Shorthand: inset: 0 (all sides) / inset-inline: 0 / inset-block: 0

Text and alignment:

  • text-align: left → text-align: start
  • float: left → float: inline-start
  • resize: horizontal → resize: inline

Flow model diagram

Architecture and Design Patterns

Pattern 1: Direction-Agnostic Components

Build components that work regardless of document direction by using only logical properties:

.card {
  inline-size: 100%;
  max-inline-size: 400px;
  margin-block: 1rem;
  margin-inline: auto;
  padding-block: 1.5rem;
  padding-inline: 2rem;
  border-inline-start: 4px solid var(--accent);
}

This card centers itself, has consistent padding that respects text direction, and adds an accent border on the "start" side — left in English, right in Arabic.

Pattern 2: Vertical Writing Support

For CJK (Chinese, Japanese, Korean) vertical layouts:

.article {
  writing-mode: vertical-rl;
  block-size: 100vh;
  max-inline-size: 80ch;
  padding-block: 2rem;
  padding-inline: 1rem;
}
 
.article h2 {
  margin-block-end: 1.5rem;
}

All your logical properties automatically adapt. The margin-block-end now appears at the bottom in vertical mode rather than below in horizontal mode.

Pattern 3: Logical Shorthands for Concise Code

/* Physical — 4 lines */
.heading {
  margin-top: 2rem;
  margin-bottom: 1rem;
  margin-left: 0;
  margin-right: 0;
}
 
/* Logical — 2 lines */
.heading {
  margin-block: 2rem 1rem;
  margin-inline: 0;
}

Pattern 4: Logical Border Radius

Border-radius also has logical equivalents:

.callout {
  border-start-start-radius: 8px;
  border-start-end-radius: 8px;
  border-end-start-radius: 0;
  border-end-end-radius: 0;
}

These map to the logical corners: start-start is top-left in LTR horizontal mode.

Step-by-Step Implementation

Step 1: Audit Existing Physical Properties

Search your codebase for physical properties that need conversion:

grep -rn 'margin-left\|margin-right\|margin-top\|margin-bottom' src/ --include="*.css" | wc -l
grep -rn 'padding-left\|padding-right' src/ --include="*.css" | wc -l
grep -rn '\bwidth\b\|\bheight\b' src/ --include="*.css" | wc -l

Step 2: Start with New Code

The easiest adoption strategy is to use logical properties for all new code. This requires zero refactoring and immediately makes new components direction-agnostic:

/* All new components use logical properties */
.search-input {
  inline-size: 100%;
  max-inline-size: 500px;
  padding-block: 0.75rem;
  padding-inline: 1rem;
  border-block-end: 2px solid var(--border);
}

Step 3: Convert Layout Components

Convert your highest-traffic layout components — navbars, sidebars, content areas — to logical properties:

.layout {
  display: grid;
  grid-template-columns: 250px 1fr;
  min-block-size: 100vh;
}
 
.sidebar {
  padding-block: 2rem;
  padding-inline: 1rem;
  border-inline-end: 1px solid var(--border);
}
 
.main-content {
  padding-block: 2rem;
  padding-inline: 3rem;
  max-inline-size: 900px;
}

Step 4: Handle the direction Attribute

Set the document direction in your HTML or via CSS:

<html lang="ar" dir="rtl">

Or dynamically:

[dir="rtl"] {
  /* No special rules needed if you used logical properties */
}

Step 5: Verify with Writing Mode Toggle

Add a dev-mode writing-mode toggle to verify your layouts:

function toggleWritingMode() {
  const html = document.documentElement;
  const current = html.getAttribute('dir');
  html.setAttribute('dir', current === 'rtl' ? 'ltr' : 'rtl');
}

Step 6: Use Stylelint to Enforce Logical Properties

{
  "plugins": ["stylelint-use-logical"],
  "rules": {
    "liberty/use-logical": ["always", {
      "except": ["width", "height"]
    }]
  }
}

This linting rule catches any physical property usage and suggests the logical equivalent.

Implementation workflow

Real-World Use Cases

Use Case 1: E-Commerce Product Page

A product page needs to support Arabic and English markets. Using logical properties, the layout flips automatically:

.product-card {
  display: flex;
  gap: 1.5rem;
  padding-block: 2rem;
  padding-inline: 1.5rem;
  border-inline-start: 3px solid var(--brand);
}
 
.product-price {
  margin-inline-start: auto;
  font-size: 1.5rem;
  font-weight: 700;
}

In Arabic (dir="rtl"), the border appears on the right and the price aligns to the left — all without a single dir-specific override.

Use Case 2: Vertical Japanese Article Layout

A Japanese magazine site uses vertical text:

.article-vertical {
  writing-mode: vertical-rl;
  font-family: "Noto Serif JP", serif;
  line-height: 1.8;
  padding-block: 3rem;
  padding-inline: 2rem;
  max-block-size: 50ch;
}
 
.article-vertical .drop-cap {
  font-size: 3rem;
  margin-inline-end: 0.5rem;
  float: inline-start;
}

Use Case 3: Dashboard Sidebar with RTL Support

.dashboard {
  display: grid;
  grid-template-columns: 260px 1fr;
  min-block-size: 100dvh;
}
 
.sidebar-nav {
  padding-block-start: 1rem;
  border-inline-end: 1px solid var(--divider);
}
 
.nav-item {
  padding-block: 0.625rem;
  padding-inline: 1.25rem;
  border-inline-start: 3px solid transparent;
}
 
.nav-item.active {
  border-inline-start-color: var(--accent);
  background: var(--accent-bg);
}

Best Practices for Production

  1. Adopt for all new code immediately: There is no migration cost for new code. Every new component and utility should use logical properties from day one.

  2. Convert incrementally: Do not attempt a big-bang rewrite. Convert components as you touch them for other reasons. This spreads the effort across sprints and reduces risk.

  3. Use the Stylelint plugin: stylelint-use-logical catches physical property usage at lint time, preventing regressions as your team adopts the pattern.

  4. Test with real RTL content: Machine-translated placeholders do not reveal layout issues. Use real Arabic or Hebrew content to verify that your logical properties produce correct layouts.

  5. Combine with dir attribute: The dir="rtl" attribute on the <html> element is the standard way to set document direction. Logical properties respond to this attribute automatically.

  6. Do not mix physical and logical on the same element: Mixing margin-left and margin-inline-start on the same selector is confusing and can produce unexpected results. Use one approach consistently per element.

  7. Remember inset for positioned elements: The inset shorthand and its logical variants (inset-block, inset-inline) are the logical equivalents of top/right/bottom/left. Use them for absolutely and fixed-positioned elements.

  8. Consider the gap property: gap in Flexbox and Grid is already writing-mode-aware. It does not need a logical variant because it already follows the flow direction.

Common Pitfalls and Solutions

PitfallImpactSolution
Mixing physical and logical on one elementUnpredictable overridesUse logical properties consistently per element
Forgetting border-radius logical variantsRounded corners do not flip in RTLUse border-start-start-radius etc.
Using width/height instead of inline-size/block-sizeLayout breaks in vertical writing modesReplace with logical sizing properties
Not testing with actual RTL contentMissed layout bugsUse real Arabic/Hebrew text in QA
Assuming text-align: left is always correctText misaligned in RTLUse text-align: start or text-align: match-parent
Using float: left/right without logical variantsFloated elements do not flipUse float: inline-start / float: inline-end

Performance Optimization

Logical properties have identical performance characteristics to their physical counterparts. The browser resolves them to physical values at computed-value time using the element's writing mode and direction. There is no runtime performance penalty for using logical properties over physical ones.

However, logical properties can reduce your CSS size when combined with logical shorthands:

/* Physical — 4 declarations */
.box {
  margin-top: 1rem;
  margin-right: 2rem;
  margin-bottom: 1rem;
  margin-left: 2rem;
}
 
/* Logical — 2 declarations (50% reduction) */
.box {
  margin-block: 1rem;
  margin-inline: 2rem;
}

Across a large codebase, this shorthand advantage adds up to meaningful savings.

Comparison with Alternatives

ApproachDirection SupportCode DuplicationBrowser SupportMaintenance
Logical PropertiesNativeNone97%+ (2024)Low
[dir="rtl"] overridesManualHighUniversalHigh
PostCSS RTLCSSAutomatedBuild-timeUniversalMedium
CSS-in-JS runtime flipRuntimeNoneUniversalMedium
Separate RTL stylesheetManualVery highUniversalVery High

Logical properties are the only approach that requires zero code duplication and zero build-time transformation. All other approaches add maintenance burden proportional to your stylesheet size.

Advanced Patterns

Logical Property Custom Properties

:root {
  --space-inline: 1.5rem;
  --space-block: 2rem;
  --border-side: 3px;
}
 
.component {
  padding-inline: var(--space-inline);
  padding-block: var(--space-block);
  border-inline-start: var(--border-side) solid var(--accent);
}

Container Query + Logical Properties

.card-container {
  container-type: inline-size;
}
 
@container (min-inline-size: 400px) {
  .card {
    display: grid;
    grid-template-columns: 1fr 2fr;
    gap: 1.5rem;
    padding-inline: 2rem;
  }
}

Logical Overflow

.scroll-container {
  overflow-inline: auto;
  overflow-block: hidden;
  max-block-size: 400px;
}

Testing Strategies

Write direction-aware tests to verify logical property behavior:

import { test, expect } from '@playwright/test';
 
test('card border flips in RTL', async ({ page }) => {
  await page.goto('/demo');
  
  // LTR: border on left
  await page.evaluate(() => document.documentElement.dir = 'ltr');
  const ltrBorder = await page.locator('.card').evaluate(
    el => getComputedStyle(el).borderInlineStartColor
  );
  
  // RTL: border should still be on inline-start (now right)
  await page.evaluate(() => document.documentElement.dir = 'rtl');
  const rtlBorder = await page.locator('.card').evaluate(
    el => getComputedStyle(el).borderInlineStartColor
  );
  
  // The logical property resolves to different physical sides
  expect(ltrBorder).not.toBe(rtlBorder);
});

Future Outlook

Logical properties are now the recommended approach in the CSS specification. The W3C has stated that physical properties will not be deprecated — they remain valid — but all new CSS features are designed with logical equivalents from the start. The CSS Working Group is also exploring logical values for transform-origin and perspective-origin, which currently only support physical coordinates. Browser DevTools increasingly highlight logical property names and show the resolved physical values, making debugging easier. Expect logical properties to become the default teaching approach in web development education within the next few years.

Cross-Browser Testing Strategy

Modern CSS features often have varying levels of browser support, making a systematic cross-browser testing strategy essential. Before using any CSS feature in production, verify its support status and implement appropriate fallbacks for browsers that haven't yet implemented it.

Progressive Enhancement with @supports

Use the @supports at-rule to provide fallback styles for browsers that don't support specific CSS features:

/* Base styles for all browsers */
.grid-container {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}
 
.grid-container > * {
  flex: 1 1 300px;
}
 
/* Enhanced layout for browsers supporting CSS Grid */
@supports (display: grid) {
  .grid-container {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 1.5rem;
  }
 
  .grid-container > * {
    flex: none;
  }
}
 
/* Further enhancement with subgrid support */
@supports (grid-template-columns: subgrid) {
  .grid-container {
    grid-template-columns: subgrid;
  }
}

Visual Regression Testing

Implement visual regression testing to catch unintended layout shifts and styling changes. Tools like Percy, Chromatic, or Playwright's screenshot comparison can detect visual differences across browsers and screen sizes:

const { test, expect } = require('@playwright/test');
 
test('responsive layout matches design', async ({ page }) => {
  await page.goto('/components/dashboard');
 
  // Test at multiple viewport sizes
  for (const viewport of [
    { width: 375, height: 812, name: 'mobile' },
    { width: 768, height: 1024, name: 'tablet' },
    { width: 1440, height: 900, name: 'desktop' },
  ]) {
    await page.setViewportSize({ width: viewport.width, height: viewport.height });
    await expect(page).toHaveScreenshot(
      `dashboard-${viewport.name}.png`,
      { maxDiffPixels: 100 }
    );
  }
});

Browser Compatibility Testing Matrix

Maintain a testing matrix that covers the browsers and versions your users actually use. Use analytics data to determine your browser support baseline, then configure tools like Browserslist to automatically handle polyfilling and prefixing:

{
  "browserslist": [
    "> 0.5%",
    "last 2 versions",
    "not dead",
    "not ie 11"
  ]
}

This data-driven approach ensures you're spending testing effort where it matters most, rather than trying to support every possible browser configuration.

Community Resources and Further Learning

The technology landscape evolves rapidly, making continuous learning essential for maintaining expertise. Building a systematic approach to staying current with developments in your technology stack ensures you can leverage new features and avoid deprecated patterns.

Curated Learning Pathways

Rather than consuming content randomly, create structured learning pathways aligned with your current projects and career goals. Start with official documentation and specification documents, which provide the most accurate and comprehensive information. Follow this with hands-on tutorials and workshops that reinforce concepts through practical application.

Technical blogs from framework maintainers and core team members often provide deeper insights into design decisions and upcoming features. Subscribe to the official blogs of your primary frameworks and libraries to stay ahead of breaking changes and deprecation timelines.

Contributing to Open Source

Contributing to open-source projects in your technology stack provides unparalleled learning opportunities. Start with documentation improvements and bug reports, then progress to fixing small issues tagged as "good first issue" in your favorite projects. This direct engagement with maintainers and the codebase accelerates your understanding far beyond what passive learning can achieve.

# Setting up for contribution
git clone https://github.com/project/repository.git
cd repository
git checkout -b fix/issue-description
 
# Run the project's contribution setup
npm run setup:dev
npm run test  # Ensure tests pass before making changes
 
# Make your changes, then run the full test suite
npm run test:full
npm run lint
npm run build
 
# Submit your contribution
git add -A
git commit -m "fix: description of the fix
 
Closes #1234"
git push origin fix/issue-description

Building a Technical Knowledge Base

Maintain a personal knowledge base that captures insights, solutions, and patterns you discover during your work. Tools like Obsidian, Notion, or even a simple Markdown repository can serve as an external memory that grows more valuable over time.

Organize your notes by topic rather than chronologically, and include code examples, links to relevant documentation, and explanations of why certain approaches work better than others. When you encounter a particularly insightful article or conference talk, write a summary that captures the key takeaways and how they apply to your current projects.

Follow key conferences and their published talks to stay informed about emerging patterns and best practices. Many conferences publish recorded talks on YouTube within weeks of the event, making world-class technical content freely accessible.

Join relevant Discord servers, Slack communities, and forums where practitioners discuss real-world challenges and solutions. These communities provide early warning about emerging issues and access to collective wisdom that isn't available through formal documentation.

Mentorship and Knowledge Sharing

Teaching others is one of the most effective ways to deepen your own understanding. Consider writing technical blog posts, giving talks at local meetups, or mentoring junior developers. The process of explaining concepts to others forces you to organize your knowledge and identify gaps in your understanding.

Pair programming sessions with colleagues of different experience levels create mutual learning opportunities. Senior developers gain fresh perspectives on problems they've solved the same way for years, while junior developers benefit from exposure to production-grade thinking and decision-making processes.

Conclusion

CSS Logical Properties are not just an internationalization tool — they are a better way to express layout intent. By mapping styles to the document's flow rather than fixed physical directions, logical properties make your code more semantically meaningful, more concise, and automatically adaptable to any writing mode.

Key takeaways:

  1. Use logical properties for all new code — zero migration cost, immediate benefits.
  2. inline-size replaces width, block-size replaces height — these are the most impactful swaps.
  3. Shorthands like margin-block and padding-inline reduce code and improve readability.
  4. Test with dir="rtl" to verify your logical properties produce correct layouts in both directions.
  5. Enforce with Stylelint — stylelint-use-logical catches physical property usage at lint time.
  6. Convert incrementally — there is no deadline. Migrate components as you touch them.

Start by replacing width/height with inline-size/block-size and margin-left/margin-right with margin-inline in your next pull request. The habit will spread naturally.