CSS Architecture

This document describes the stylesheet structure, class-naming convention, and customization points for the <vn-resize-box> component.


1. Single Stylesheet

All component styles live in src/vanilla-resize-box/vanilla-resize-box.css. The file is automatically injected into <head> by the web component on first connection. Multiple <vn-resize-box> elements share a single <link> tag (identified by id="vn-resize-box-css").


2. CSS Class Inventory

Class Element Purpose
vn-resize-box Custom element The sized layout item — display: block, box-sizing: border-box; receives the width/height/min-*/max-* inline styles
.vn-resize-box Wrapper div Structural container — position: relative, box-sizing: border-box, overflow: auto
.vn-resize-handle Handle div Drag handle in the bottom inline-end corner — position: absolute, touch-action: none, cursor: se-resize (sw-resize in RTL)
.vn-resize-box--dragging Wrapper div Added during active drag — disables child pointer-events, shows resize cursor

All classes use the vn- prefix to avoid conflicts with host application styles.


3. Custom element (vn-resize-box)

vn-resize-box {
    display: block;
    box-sizing: border-box;
}

display: block because browsers lay unknown elements out as inline. box-sizing: border-box is a correctness requirement, not a cosmetic default: the drag code measures the element with getBoundingClientRect() — a border-box width — and writes that number straight back into style.width. Under the default content-box, a host that put padding or a border on the custom element would have that chrome added again on every write, inflating the box by twice its chrome on each drag.


4. Container (.vn-resize-box)

.vn-resize-box {
    position: relative;
    box-sizing: border-box;
    overflow: auto;
    resize: none;
}

4.1 Child Sizing & Stacking Containment

.vn-resize-box > :not(.vn-resize-handle) {
    width: 100%;
    height: 100%;
    isolation: isolate;
}

Direct children (except the handle) are stretched to fill the box. This makes it easy to wrap a <vn-grid> or any block element without extra sizing rules.

The component therefore supports exactly one content child. Two direct children each render at the full box height — 2× the box — and overflow into the scrollbars overflow: auto then produces. This is a contract, not a bug to work around: a host that needs several elements wraps them in a single <div> and lays them out inside it.

isolation: isolate makes each content child an atomic stacking layer at z-index: auto. Every z-index the child uses internally (e.g. a grid's custom scrollbar tracks at z-index: 10) is resolved inside that layer and can no longer compete with the sibling .vn-resize-handle — so the handle's z-index: 1 always wins the paint order and pointer hit test in the bottom-right corner, regardless of what the child renders there. Without it, any positioned child descendant with z-index ≥ 1 in that corner would cover the handle and make the resize affordance unreachable.


5. Handle (.vn-resize-handle)

.vn-resize-handle {
    position: absolute;
    inset-inline-end: 0;
    inset-block-end: 0;
    width: 24px;
    height: 24px;
    pointer-events: auto;
    touch-action: none;
    cursor: se-resize;
    overflow: hidden;
    border-radius: 0 0 5px 0;
    z-index: 1;
}

The handle is a 24×24px hotspot pinned to the bottom corner on the inline-end side. z-index: 1 is sufficient to keep it above any content child because children are stacking-isolated (see §4.1) — do not raise it defensively.

touch-action: none is required, not decorative. The drag runs on Pointer Events, so without it the browser claims a touch on the handle for panning and the gesture scrolls the page instead of resizing the box.

inset-inline-end / inset-block-end are logical, so the handle moves to the bottom-left corner under dir="rtl" with no extra rule — see §5.3.

5.1 Triangle Visual

.vn-resize-handle::before {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(135deg, transparent 50%, rgba(128, 128, 128, 0.16) 50%);
}

A CSS gradient creates a triangular visual in the bottom-right half of the handle square. Only the triangle area is visually prominent; the top-left half is transparent.

5.2 Hover/Active Feedback

.vn-resize-handle:hover::before,
.vn-resize-handle:active::before {
    background: linear-gradient(135deg, transparent 50%, rgba(80, 120, 220, 0.28) 50%);
}

On hover or active press, the triangle shifts to a blue tint for visual affordance.

5.3 RTL Mirroring

Logical insets move the handle to the correct corner on their own. Three things are physical and are mirrored by hand:

[dir="rtl"] .vn-resize-handle {
    cursor: sw-resize;
    border-radius: 0 0 0 5px;
}

[dir="rtl"] .vn-resize-handle::before {
    background: linear-gradient(225deg, transparent 50%, rgba(128, 128, 128, 0.16) 50%);
}

[dir="rtl"] .vn-resize-box--dragging {
    cursor: sw-resize;
}

These key off the dir attribute, so an RTL host must set dir="rtl" — on <html>, on the box, or on any ancestor — rather than only the CSS direction property. The JavaScript half reads computed direction, which is the honest source for the drag arithmetic; a host that sets direction without dir still resizes correctly but keeps the LTR cursor and triangle.

Only the inline axis mirrors. Vertical writing modes are not supported.


6. Dragging State (.vn-resize-box--dragging)

.vn-resize-box--dragging {
    cursor: se-resize;
}
.vn-resize-box--dragging > :not(.vn-resize-handle) {
    pointer-events: none;
}

During an active drag:


7. Customization Points

Because the component uses light DOM, host applications can override any style using normal CSS specificity.

7.1 Changing the Handle Appearance

/* Larger handle */
.vn-resize-handle {
    width: 28px;
    height: 28px;
}

/* Different color */
.vn-resize-handle::before {
    background: linear-gradient(135deg, transparent 50%, rgba(0, 120, 215, 0.3) 50%);
}

7.2 Adding Visual Chrome to the Box

.vn-resize-box {
    border: 1px solid rgba(0, 0, 0, 0.08);
    border-radius: 8px;
    padding: 6px;
    background: #ffffff;
}

7.3 Theme-Specific Overrides

Use CSS custom properties or scoped selectors:

/* Dark theme */
[data-theme="dark"] .vn-resize-box {
    border-color: rgba(255, 255, 255, 0.1);
    background: #1e1e1e;
}

[data-theme="dark"] .vn-resize-handle::before {
    background: linear-gradient(135deg, transparent 50%, rgba(200, 200, 200, 0.2) 50%);
}

8. No Shadow DOM Implications

Because the component attaches all elements to the light DOM: