Components/Resizeable

Resizeable

Two panels and the twelve pixels between them: a 1px rule you can see, a 12-wide board you can grab, and a grip that says the rule moves.

Resizeable · Components frameDrag the handle, or focus it and use the arrow keys · hover is not a state
Handle State
One
Two
480 × 480 in the file234 · 12 · 23450% / 50%
Twelve pixels around a one-pixel line

What you see is a 1px base.border rule. What you can hit is a 12-wide board centred on it. The handle is a target before it is a decoration, and drawing the board rather than just the line is what records that in the file - a designer placing it knows to leave 12, not 1.

Two states, and hover is not one of them

Focus swaps the line and the grip's edge from base.border to base.ring. No offset ring is drawn, because a 12-wide strip has nowhere to put one. Hovering changes nothing at all: the source styles only focus-visible, so the cursor turning into a resize arrow is the whole hover affordance, and the file is right to draw two states rather than three.

The panels are deliberately empty

Each panel holds one centred label and nothing else. A panel is a region, not a surface: it has no fill, no padding and no edge of its own, so whatever you drop in sits directly on the group's background and the only line between panels is the handle.

What the two variants leave out

Vertical and bare are props, not drawings

The source has an orientation of horizontal or vertical on the group, and a withHandle boolean on the handle. Neither is in the file. Both are shown here so you can see what you are choosing when you set them in code, and decide whether the kit needs to draw them.

orientation · withHandleLive in the browser · drawn to match the file
Orientation
Handle
One
Two

Without the grip the line is still draggable and still 12 wide, but nothing announces it. The source defaults withHandle to false and the docs turn it on in every example, which tells you which one people actually ship. Vertical is the same handle turned ninety degrees: a 16 by 6 grip on a full-width rule.

The usage frame

Two panels, then three

1104 by 672, holding an editor split in two and a three-panel workspace. The left one is a real instance of the component resized to 440 by 400; the right one is three panels and two handles, which the component itself cannot become because a copy in Penpot cannot take a third panel.

Frame
Files
Canvas
440 × 400 · 214 · 12 · 214
Five layouts

The component is the frame, not the furniture

Every layout below is the same two parts with different things inside them. Drag any handle; each one also takes the arrow keys once it has focus, which is the part of the source the kit cannot draw but a keyboard user relies on.

Layout
srccomponents button.tsx card.tsx input.tsxhooksutils.ts
button.tsxexport function Button( props: ButtonProps) { // one row, two slots return <button {...props} />}
26% / 74%

A minimum is the one thing a drawing cannot carry. The file can show you a panel at 152 and a panel at 214, but not that it must never go under 120 - that lives in the code, and it is what stops a drag from squeezing a label out of existence. Keep the floor above the width of the widest thing the panel has to show at rest.

In the file

Two components, two variants

Read straight out of the Penpot file. The axes below are the property names you will see in the variant dropdown.

Resizable / HandleStateDefault · Focus - 12 × 480, the line and the grip edge move from base.border to base.ring2
Resizablenone480 × 480 - two 234 panels with a handle between them1

Using the Resizeable

Because the line is what you see and the 12 is what you hit. A one-pixel drag target is unusable with a mouse and impossible with a trackpad, so the component draws the board at the size of the gesture and centres the rule inside it. Placing it means leaving 12 between the panels, not 1, and that is exactly what the file records.

Announcing that the line moves. Without it a divider looks like a border, and nobody tries to drag a border. It is 6 by 16 on the group's background inside a base.input edge, so it reads as a small raised tab sitting on the rule rather than a mark drawn on it. The source calls this withHandle and defaults it off; every example in the docs turns it on.

Because there is nowhere to put a ring. The kit's focus ring sits outside the thing it surrounds, and outside a 12-wide strip is the middle of the panel on either side. So Focus recolours both the line and the grip's edge to base.ring, which is visible along the full height of the split and cannot collide with anything.

No, and there should not be. The source styles focus-visible and nothing else on the handle, so hovering leaves every pixel where it was - the cursor turning into a resize arrow is the entire affordance. A hover variant would be inventing a state the component does not have, and would then have to answer what happens when a pointer hovers a handle that already has keyboard focus.

Not in the file. The source has orientation set to horizontal or vertical on the group, and the vertical case is the same handle turned ninety degrees - a full-width rule with a 16 by 6 grip. It is shown on this page so you can see what you get, but nothing has been added to the file for it.

Because a panel is a region, not a surface. It has no fill, no padding and no edge of its own; the only line in the whole component is the handle. Whatever you put in a panel sits straight on the group's background, which is why the file draws a single centred label and stops.

Those live in the code, not the drawing. The source gives each panel a defaultSize, a minSize and a maxSize, and a file can only ever show you one arrangement of the three. The rule worth keeping is that the floor should be wider than the widest thing the panel has to show at rest, or a drag can squeeze a label out of existence.

Layout · 2 variants