Vanilla Resize Box

A lightweight, zero-dependency, resizable container web component with a visible bottom-right drag handle. Built with vanilla JavaScript — no frameworks needed.

Features

Installation

Direct File Include

<script src="path/to/vanilla-resize-box/vanilla-resize-box.js"></script>

The component automatically injects its CSS (vanilla-resize-box.css) into <head>. No manual <link> tag is required.

Usage

Basic Usage

<vn-resize-box width="600" height="400">
    <p>Any content goes here</p>
</vn-resize-box>

With Constraints

<vn-resize-box
    width="800"
    height="500"
    min-width="300"
    min-height="200"
    max-width="1200"
    max-height="800">
    <div id="myContent">Resizable content</div>
</vn-resize-box>

Wrapping a Grid Component

<vn-resize-box min-width="360" min-height="220">
    <vn-grid id="usersGrid" theme="default">
        <vn-grid-column field="Name" header="Name" type="string"></vn-grid-column>
        <vn-grid-column field="Email" header="Email" type="string"></vn-grid-column>
    </vn-grid>
</vn-resize-box>

No Initial Size (Fills Parent)

When width and height are omitted, the box fills its parent container:

<div style="width: 100%; height: 80vh;">
    <vn-resize-box>
        <p>Fills parent, then can be resized</p>
    </vn-resize-box>
</div>

Attributes

All attributes are optional.

Attribute Type Default Description
width CSS length Initial width. A bare number is px; any CSS length works (600, 600px, 100%, 60vw). If omitted, the container fills its parent's width
height CSS length Initial height. Same syntax as width. If omitted, the container fills its parent's height
min-width px 100 Minimum width. Bare number or explicit px only — a relative value is rejected and the default applies
min-height px 100 Minimum height. Same rule as min-width
max-width CSS length Maximum width. Same syntax as width. If omitted, limited by parent bounds
max-height CSS length Maximum height. Same syntax as width. If omitted, limited by parent bounds

Removing any of the sizing attributes clears the inline style it wrote, so the dimension falls back to whatever CSS says.

Properties

All are read-only; attributes are the configuration channel and resize() is the write path for size.

Property Type Description
width string | null The width attribute, verbatim — width="600" reads back "600", not "600px"
height string | null The height attribute, verbatim
maxWidth string | null The max-width attribute, verbatim
maxHeight string | null The max-height attribute, verbatim
minWidth number The minimum width actually enforced, in px. Resolved, not raw: a rejected min-width reads back as the default (100)
minHeight number The minimum height actually enforced, in px
currentWidth number Current rendered width in pixels
currentHeight number Current rendered height in pixels

Methods

Method Signature Description
resize resize(width?, height?) Programmatically resize the box. Numbers are clamped to the min-*/max-* attributes; CSS strings are applied as-is
const box = document.querySelector('vn-resize-box');
box.resize(500, 300);

resize() deliberately differs from a drag in three ways:

Events

Event Detail Description
vn-resize-start { width, height } Fired when the user begins dragging the handle, carrying the size at press time
vn-resize-move { width, height } Fired on every frame during drag
vn-resize-end { width, height } Fired when the user releases the handle — and by resize()
document.querySelector('vn-resize-box').addEventListener('vn-resize-end', (e) => {
    console.log('New size:', e.detail.width, 'x', e.detail.height);
});

Accessibility

Resizing is pointer-only. The drag handle carries role="separator" and an accessible name so assistive technology announces the resizable boundary, but it is deliberately not focusable and answers to no key — it adds no tab stop to the page, in particular none between the page and the content the box wraps. A host that needs a keyboard-operable size drives resize() from its own control.

Labelling. Put aria-label on the <vn-resize-box> and the handle takes it with a suffix:

<vn-resize-box aria-label="Resizable orders grid">…</vn-resize-box>
<!-- handle: aria-label="Resizable orders grid — resize handle" -->

With no host label the handle falls back to "Resize". The suffix is hard-coded English (the component ships no i18n mechanism) and the name is derived once, when the internal markup is built — set the handle's aria-label directly if you need another language or a runtime change.

The handle carries no aria-valuenow / aria-orientation: it drives two axes at once, so neither could be stated honestly.

Right-to-Left

The component follows the inline direction. Under dir="rtl" — set on <html>, on the box, or on any ancestor — the handle sits in the bottom-left corner, dragging left grows the box, and the parent-relative ceiling is measured from the parent's right edge. There is no attribute to switch this on.

The mirrored cursor, corner radius and triangle are [dir="rtl"] CSS rules, so set the dir attribute rather than only the CSS direction property: with direction alone the box still resizes correctly, but the handle keeps its LTR visual.

Vertical writing modes are not supported — only the inline axis mirrors.

CSS Customization

The component uses light DOM, so you can override styles with normal CSS selectors:

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

/* Add a border to the container */
.vn-resize-box {
    border: 1px solid #ccc;
    border-radius: 8px;
    padding: 6px;
}

CSS Classes

Class Element Description
.vn-resize-box Container div The outer resizable container
.vn-resize-handle Handle div The bottom-right drag handle
.vn-resize-box--dragging Container div Added during active drag

Architecture

The component uses light DOM (no Shadow DOM) so that existing app and theme CSS continues to apply to slotted content. It auto-injects a single shared <link> stylesheet into <head> on first use.

Children may be added at any time: content present at connect is wrapped into the internal .vn-resize-box structural div, and children appended after connect are adopted into it automatically (frameworks like Angular attach the element to the document before rendering its children).

File Structure

vanilla-resize-box/
├── vanilla-resize-box.js    # Web component definition
├── vanilla-resize-box.css   # Base structural styles
├── vanilla-resize-box.d.ts  # Hand-authored TypeScript declarations
└── README.md                # This file

Browser Support

Works in all modern browsers that support Custom Elements v1 (Chrome, Firefox, Safari, Edge).

Changelog

Version 1.4.0

Keyboard resizing removed — the handle is pointer-only.

Breaking:

Version 1.3.1

Version 1.3.0

Pointer Events, keyboard accessibility and RTL, plus a batch of correctness fixes.

New:

Fixed:

Changed (resize()):

Docs:

Version 1.2.3

Version 1.2.2

Version 1.2.1

Version 1.2.0

Version 1.1.2

Version 1.1.1

Version 1.1.0

Version 1.0.0

License

MIT