# Resizable side panel with snap points

> A details panel you stretch from its handle that snaps to three widths; its content rearranges itself with container queries.

- Category: [Overlays](https://library.aimran.es/components/overlays)
- Built with: HTML + CSS
- Live editor: https://library.aimran.es/blocks/uv-ov-resizable-panel
- Library: AIMRAN Library (https://library.aimran.es), free, no sign-up, by Aimran Oukham

## Where to use it

Inspector panels never have the right width, so this one lets people choose. Drag the handle and the panel follows, dashed guides show three snap widths and letting go springs to the nearest one, or closes it if you pull it almost shut. It fits photo libraries, file managers, admin tables and mail clients.

The main color is the handle and the primary action, the second one its soft background. The handle is a separator you can focus and move with the arrow keys, Home and End, and Escape closes the panel with focus back on the selected item.

## Code

### HTML

```html
<div class="uvq13-rsz"><p class="tt">Photos</p><div class="gr"><button type="button" class="ph" style="--a:#f97316;--b:#7c3aed" data-d="4032 × 3024" aria-label="IMG_0412.jpg" aria-haspopup="dialog"></button><button type="button" class="ph" style="--a:#0ea5e9;--b:#14532d" data-d="3024 × 4032" aria-label="IMG_0418.jpg" aria-haspopup="dialog"></button><button type="button" class="ph" style="--a:#facc15;--b:#dc2626" data-d="4032 × 2268" aria-label="IMG_0433.jpg" aria-haspopup="dialog"></button><button type="button" class="ph" style="--a:#64748b;--b:#0f172a" data-d="4032 × 3024" aria-label="IMG_0451.jpg" aria-haspopup="dialog"></button><button type="button" class="ph" style="--a:#f472b6;--b:#0e7490" data-d="3024 × 3024" aria-label="IMG_0460.jpg" aria-haspopup="dialog"></button><button type="button" class="ph" style="--a:#a3e635;--b:#166534" data-d="4032 × 3024" aria-label="IMG_0477.jpg" aria-haspopup="dialog"></button></div><span class="snap" aria-hidden="true"><i style="--d:7.5"></i><i style="--d:11.5"></i><i style="--d:16"></i></span><aside class="pn" role="dialog" aria-labelledby="uvq13-rsz-n"><div class="hd" role="separator" tabindex="0" aria-orientation="vertical" aria-label="Panel width" aria-valuemin="0" aria-valuemax="2" aria-valuenow="1"><i></i></div><div class="in"><header><b class="fn" id="uvq13-rsz-n"></b><button type="button" class="x" aria-label="Close"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 7l10 10M17 7L7 17"/></svg></button></header><i class="pre" aria-hidden="true"></i><dl class="mt"><div><dt>Dimensions</dt><dd class="dd"></dd></div><div><dt>Size</dt><dd>4,2 MB</dd></div><div><dt>Camera</dt><dd>f/1.8 · 1/250</dd></div><div><dt>Date</dt><dd>12/10/2026</dd></div></dl><div class="act"><button type="button"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 4v11M7.5 10.5L12 15l4.5-4.5M5 19.5h14"/></svg><span>Download</span></button><button type="button"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 7h14M10 11v6M14 11v6M6.5 7l1 12.5h9l1-12.5M9.5 7V4.5h5V7"/></svg><span>Delete</span></button></div></div></aside></div>
```

### CSS

```css
.uvq13-rsz {
  --c1: #db2777;
  --c2: #fce7f3;
  --s: 16px;
  /* tamaño */
  --k: 1;
  /* velocidad */
  --fg: #1d1d1f;
  /* texto */
  --bg: #f5f5f7;
  /* fondo */
}

.uvq13-rsz {
  font-family: -apple-system,BlinkMacSystemFont,"SF Pro Text","Inter",system-ui,sans-serif;
  font-size: var(--s);
  color: var(--fg);
  -webkit-tap-highlight-color: transparent;
  position: relative;
  width: 22em;
  height: 18em;
  border-radius: 1.2em;
  overflow: hidden;
  background: color-mix(in srgb,var(--fg) 3%,var(--bg));
  box-shadow: 0 0 0 1px color-mix(in srgb,var(--fg) 9%,transparent);
}

.uvq13-rsz .tt {
  margin: 0;
  padding: 1em 1.1em .5em;
  font-weight: 800;
}

.uvq13-rsz .gr {
  display: grid;
  grid-template-columns: repeat(3,1fr);
  gap: .4em;
  padding: 0 1.1em;
}

.uvq13-rsz .ph {
  font: inherit;
  color: inherit;
  border: 0;
  margin: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  aspect-ratio: 1;
  border-radius: .6em;
  background: radial-gradient(circle at 70% 30%,rgba(255,255,255,.5) 0 12%,transparent 13%),linear-gradient(160deg,var(--a),var(--b));
  transition: transform calc(0.2s / var(--k)) cubic-bezier(.34,1.56,.64,1),box-shadow calc(0.2s / var(--k)) cubic-bezier(.25,.1,.25,1);
}

.uvq13-rsz .ph:hover {
  transform: scale(1.03);
}

.uvq13-rsz .ph.on {
  box-shadow: 0 0 0 2px var(--bg),0 0 0 4px var(--c1);
}

.uvq13-rsz .ph:focus-visible {
  outline: 2px solid var(--c1);
  outline-offset: 3px;
}

.uvq13-rsz .snap {
  position: absolute;
  inset: 0;
  z-index: 6;
  pointer-events: none;
  opacity: 0;
  transition: opacity calc(0.2s / var(--k)) cubic-bezier(.25,.1,.25,1);
}

.uvq13-rsz.rs .snap {
  opacity: 1;
}

.uvq13-rsz .snap i {
  position: absolute;
  top: 0;
  bottom: 0;
  right: calc(var(--d) * 1em);
  border-left: 1.5px dashed color-mix(in srgb,var(--c1) 70%,transparent);
}

.uvq13-rsz .pn {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 5;
  width: calc(var(--w,11.5) * 1em);
  box-sizing: border-box;
  container-type: inline-size;
  background: var(--bg);
  box-shadow: -1px 0 0 color-mix(in srgb,var(--fg) 9%,transparent),-1em 0 2em -1em rgba(0,0,0,.35);
  transform: translateX(105%);
  transition: transform calc(0.45s / var(--k)) cubic-bezier(.22,1,.36,1),width calc(0.45s / var(--k)) cubic-bezier(.34,1.56,.64,1);
}

.uvq13-rsz.open .pn {
  transform: none;
}

.uvq13-rsz .pn.dr {
  transition: none;
}

.uvq13-rsz .hd {
  position: absolute;
  left: -.6em;
  top: 0;
  bottom: 0;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 1.2em;
  cursor: ew-resize;
  touch-action: none;
  outline: 0;
}

.uvq13-rsz .hd i {
  width: .32em;
  height: 2.6em;
  border-radius: 1em;
  background: color-mix(in srgb,var(--fg) 22%,transparent);
  transition: background calc(0.2s / var(--k)) cubic-bezier(.25,.1,.25,1),height calc(0.3s / var(--k)) cubic-bezier(.34,1.56,.64,1);
}

.uvq13-rsz .hd:hover i,.uvq13-rsz .dr .hd i,.uvq13-rsz .hd:focus-visible i {
  height: 3.8em;
  background: var(--c1);
}

.uvq13-rsz .hd:focus-visible i {
  box-shadow: 0 0 0 3px color-mix(in srgb,var(--c1) 30%,transparent);
}

.uvq13-rsz .in {
  display: flex;
  flex-direction: column;
  gap: .5em;
  height: 100%;
  padding: .8em .9em .9em 1.1em;
  box-sizing: border-box;
  overflow: hidden;
}

.uvq13-rsz header {
  display: flex;
  align-items: center;
  gap: .4em;
}

.uvq13-rsz .fn {
  flex: 1;
  min-width: 0;
  font-size: .74em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.uvq13-rsz .x {
  font: inherit;
  color: inherit;
  border: 0;
  margin: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  display: grid;
  place-items: center;
  flex: none;
  width: 1.8em;
  height: 1.8em;
  border-radius: 50%;
  background: color-mix(in srgb,var(--fg) 6%,transparent);
}

.uvq13-rsz .x svg {
  width: .9em;
  height: .9em;
}

.uvq13-rsz .pre {
  display: block;
  flex: none;
  aspect-ratio: 16/9;
  border-radius: .6em;
  background: radial-gradient(circle at 70% 30%,rgba(255,255,255,.5) 0 12%,transparent 13%),linear-gradient(160deg,var(--a,#ccc),var(--b,#999));
}

.uvq13-rsz .mt {
  display: none;
  margin: 0;
}

.uvq13-rsz .mt div {
  display: flex;
  justify-content: space-between;
  gap: .5em;
  padding: .3em 0;
  font-size: .66em;
  border-bottom: 1px solid color-mix(in srgb,var(--fg) 9%,transparent);
}

.uvq13-rsz dt {
  color: color-mix(in srgb,var(--fg) 55%,transparent);
}

.uvq13-rsz dd {
  margin: 0;
  font-weight: 650;
  text-align: right;
}

.uvq13-rsz .act {
  display: flex;
  flex-direction: column;
  gap: .35em;
  margin-top: auto;
}

.uvq13-rsz .act button {
  font: inherit;
  color: inherit;
  border: 0;
  margin: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .4em;
  padding: .55em;
  border-radius: .6em;
  font-size: .72em;
  font-weight: 700;
  background: color-mix(in srgb,var(--fg) 6%,transparent);
}

.uvq13-rsz .act button:first-child {
  color: var(--c1);
  background: var(--c2);
}

.uvq13-rsz .act svg {
  width: 1.2em;
  height: 1.2em;
}

.uvq13-rsz .act span {
  display: none;
}

.uvq13-rsz button:focus-visible {
  outline: 2px solid var(--c1);
  outline-offset: 3px;
}

@container (min-width: 10em) {
  .uvq13-rsz .mt {
    display: block;
  }
  .uvq13-rsz .act {
    flex-direction: row;
  }
  .uvq13-rsz .act button {
    flex: 1;
  }
  .uvq13-rsz .act button:first-child span {
    display: inline;
  }
  .uvq13-rsz .fn {
    font-size: .85em;
  }
}

@container (min-width: 14em) {
  .uvq13-rsz .act span {
    display: inline;
  }
  .uvq13-rsz .in {
    display: grid;
    grid-template-columns: 1.1fr 1fr;
    grid-template-rows: auto 1fr auto;
    align-content: start;
  }
  .uvq13-rsz header {
    grid-column: 1/-1;
  }
  .uvq13-rsz .pre {
    grid-row: 2;
    aspect-ratio: auto;
    height: 100%;
  }
  .uvq13-rsz .mt {
    grid-row: 2;
  }
  .uvq13-rsz .act {
    grid-column: 1/-1;
  }
  .uvq13-rsz .fn {
    font-size: 1em;
  }
}

@media (prefers-reduced-motion:reduce) {
  .uvq13-rsz,.uvq13-rsz *,.uvq13-rsz *::before,.uvq13-rsz *::after {
    animation-duration: .01ms!important;
    animation-iteration-count: 1!important;
    transition-duration: .01ms!important;
  }
}
```

### JavaScript

```js
document.querySelectorAll(".uvq13-rsz").forEach(el => {
  const pn=el.querySelector(".pn"),hd=el.querySelector(".hd"),phs=[...el.querySelectorAll(".ph")],D=[7.5,11.5,16];let w=11.5,d=null,cur=null,tms=[];const later=(f,ms)=>tms.push(setTimeout(f,ms));
const isO=()=>el.classList.contains("open");
const setW=v=>{w=v;pn.style.setProperty("--w",v.toFixed(2));const i=D.indexOf(v);if(i>-1)hd.setAttribute("aria-valuenow",String(i))};
const open=(b,u)=>{phs.forEach(x=>x.classList.toggle("on",x===b));cur=b;pn.querySelector(".fn").textContent=b.getAttribute("aria-label");pn.querySelector(".dd").textContent=b.dataset.d;pn.style.setProperty("--a",b.style.getPropertyValue("--a"));pn.style.setProperty("--b",b.style.getPropertyValue("--b"));el.classList.add("open");pn.inert=false;u&&later(()=>hd.focus({preventScroll:true}),350)};
const close=()=>{if(!isO())return;el.classList.remove("open");pn.inert=true;phs.forEach(x=>x.classList.remove("on"));cur&&cur.focus({preventScroll:true});later(()=>!isO()&&setW(11.5),460)};
phs.forEach(b=>b.addEventListener("click",()=>open(b,true)));pn.querySelector(".x").addEventListener("click",close);
hd.addEventListener("pointerdown",e=>{d={x:e.clientX,w,s:el.getBoundingClientRect().width/el.offsetWidth||1,f:parseFloat(getComputedStyle(el).fontSize)};hd.setPointerCapture(e.pointerId);pn.classList.add("dr");el.classList.add("rs")});
hd.addEventListener("pointermove",e=>{if(d)setW(Math.max(3,Math.min(19,d.w-(e.clientX-d.x)/d.s/d.f)))});
const up=()=>{if(!d)return;d=null;pn.classList.remove("dr");el.classList.remove("rs");if(w<5.2){close();return}setW(D.reduce((a,b)=>Math.abs(b-w)<Math.abs(a-w)?b:a))};
hd.addEventListener("pointerup",up);hd.addEventListener("pointercancel",up);
hd.addEventListener("keydown",e=>{const i=D.indexOf(w);if(e.key==="ArrowLeft"){e.preventDefault();setW(D[Math.min(2,i+1)])}else if(e.key==="ArrowRight"){e.preventDefault();i<=0?close():setW(D[i-1])}else if(e.key==="Home"){e.preventDefault();setW(D[2])}else if(e.key==="End"){e.preventDefault();setW(D[0])}});
el.addEventListener("keydown",e=>{if(e.key==="Escape"&&isO()){e.preventDefault();close()}});
pn.inert=true;setW(11.5);later(()=>open(phs[1],false),600);return()=>tms.forEach(clearTimeout)
});
```

### React

```tsx
// @ts-nocheck: setup() es JavaScript del DOM sin tipos, el mismo de la pestaña JS.
import { useEffect, useRef, type CSSProperties } from "react";

const css = `
.uvq13-rsz {
  font-family: -apple-system,BlinkMacSystemFont,"SF Pro Text","Inter",system-ui,sans-serif;
  font-size: var(--s);
  color: var(--fg);
  -webkit-tap-highlight-color: transparent;
  position: relative;
  width: 22em;
  height: 18em;
  border-radius: 1.2em;
  overflow: hidden;
  background: color-mix(in srgb,var(--fg) 3%,var(--bg));
  box-shadow: 0 0 0 1px color-mix(in srgb,var(--fg) 9%,transparent);
}

.uvq13-rsz .tt {
  margin: 0;
  padding: 1em 1.1em .5em;
  font-weight: 800;
}

.uvq13-rsz .gr {
  display: grid;
  grid-template-columns: repeat(3,1fr);
  gap: .4em;
  padding: 0 1.1em;
}

.uvq13-rsz .ph {
  font: inherit;
  color: inherit;
  border: 0;
  margin: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  aspect-ratio: 1;
  border-radius: .6em;
  background: radial-gradient(circle at 70% 30%,rgba(255,255,255,.5) 0 12%,transparent 13%),linear-gradient(160deg,var(--a),var(--b));
  transition: transform calc(0.2s / var(--k)) cubic-bezier(.34,1.56,.64,1),box-shadow calc(0.2s / var(--k)) cubic-bezier(.25,.1,.25,1);
}

.uvq13-rsz .ph:hover {
  transform: scale(1.03);
}

.uvq13-rsz .ph.on {
  box-shadow: 0 0 0 2px var(--bg),0 0 0 4px var(--c1);
}

.uvq13-rsz .ph:focus-visible {
  outline: 2px solid var(--c1);
  outline-offset: 3px;
}

.uvq13-rsz .snap {
  position: absolute;
  inset: 0;
  z-index: 6;
  pointer-events: none;
  opacity: 0;
  transition: opacity calc(0.2s / var(--k)) cubic-bezier(.25,.1,.25,1);
}

.uvq13-rsz.rs .snap {
  opacity: 1;
}

.uvq13-rsz .snap i {
  position: absolute;
  top: 0;
  bottom: 0;
  right: calc(var(--d) * 1em);
  border-left: 1.5px dashed color-mix(in srgb,var(--c1) 70%,transparent);
}

.uvq13-rsz .pn {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 5;
  width: calc(var(--w,11.5) * 1em);
  box-sizing: border-box;
  container-type: inline-size;
  background: var(--bg);
  box-shadow: -1px 0 0 color-mix(in srgb,var(--fg) 9%,transparent),-1em 0 2em -1em rgba(0,0,0,.35);
  transform: translateX(105%);
  transition: transform calc(0.45s / var(--k)) cubic-bezier(.22,1,.36,1),width calc(0.45s / var(--k)) cubic-bezier(.34,1.56,.64,1);
}

.uvq13-rsz.open .pn {
  transform: none;
}

.uvq13-rsz .pn.dr {
  transition: none;
}

.uvq13-rsz .hd {
  position: absolute;
  left: -.6em;
  top: 0;
  bottom: 0;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 1.2em;
  cursor: ew-resize;
  touch-action: none;
  outline: 0;
}

.uvq13-rsz .hd i {
  width: .32em;
  height: 2.6em;
  border-radius: 1em;
  background: color-mix(in srgb,var(--fg) 22%,transparent);
  transition: background calc(0.2s / var(--k)) cubic-bezier(.25,.1,.25,1),height calc(0.3s / var(--k)) cubic-bezier(.34,1.56,.64,1);
}

.uvq13-rsz .hd:hover i,.uvq13-rsz .dr .hd i,.uvq13-rsz .hd:focus-visible i {
  height: 3.8em;
  background: var(--c1);
}

.uvq13-rsz .hd:focus-visible i {
  box-shadow: 0 0 0 3px color-mix(in srgb,var(--c1) 30%,transparent);
}

.uvq13-rsz .in {
  display: flex;
  flex-direction: column;
  gap: .5em;
  height: 100%;
  padding: .8em .9em .9em 1.1em;
  box-sizing: border-box;
  overflow: hidden;
}

.uvq13-rsz header {
  display: flex;
  align-items: center;
  gap: .4em;
}

.uvq13-rsz .fn {
  flex: 1;
  min-width: 0;
  font-size: .74em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.uvq13-rsz .x {
  font: inherit;
  color: inherit;
  border: 0;
  margin: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  display: grid;
  place-items: center;
  flex: none;
  width: 1.8em;
  height: 1.8em;
  border-radius: 50%;
  background: color-mix(in srgb,var(--fg) 6%,transparent);
}

.uvq13-rsz .x svg {
  width: .9em;
  height: .9em;
}

.uvq13-rsz .pre {
  display: block;
  flex: none;
  aspect-ratio: 16/9;
  border-radius: .6em;
  background: radial-gradient(circle at 70% 30%,rgba(255,255,255,.5) 0 12%,transparent 13%),linear-gradient(160deg,var(--a,#ccc),var(--b,#999));
}

.uvq13-rsz .mt {
  display: none;
  margin: 0;
}

.uvq13-rsz .mt div {
  display: flex;
  justify-content: space-between;
  gap: .5em;
  padding: .3em 0;
  font-size: .66em;
  border-bottom: 1px solid color-mix(in srgb,var(--fg) 9%,transparent);
}

.uvq13-rsz dt {
  color: color-mix(in srgb,var(--fg) 55%,transparent);
}

.uvq13-rsz dd {
  margin: 0;
  font-weight: 650;
  text-align: right;
}

.uvq13-rsz .act {
  display: flex;
  flex-direction: column;
  gap: .35em;
  margin-top: auto;
}

.uvq13-rsz .act button {
  font: inherit;
  color: inherit;
  border: 0;
  margin: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .4em;
  padding: .55em;
  border-radius: .6em;
  font-size: .72em;
  font-weight: 700;
  background: color-mix(in srgb,var(--fg) 6%,transparent);
}

.uvq13-rsz .act button:first-child {
  color: var(--c1);
  background: var(--c2);
}

.uvq13-rsz .act svg {
  width: 1.2em;
  height: 1.2em;
}

.uvq13-rsz .act span {
  display: none;
}

.uvq13-rsz button:focus-visible {
  outline: 2px solid var(--c1);
  outline-offset: 3px;
}

@container (min-width: 10em) {
  .uvq13-rsz .mt {
    display: block;
  }
  .uvq13-rsz .act {
    flex-direction: row;
  }
  .uvq13-rsz .act button {
    flex: 1;
  }
  .uvq13-rsz .act button:first-child span {
    display: inline;
  }
  .uvq13-rsz .fn {
    font-size: .85em;
  }
}

@container (min-width: 14em) {
  .uvq13-rsz .act span {
    display: inline;
  }
  .uvq13-rsz .in {
    display: grid;
    grid-template-columns: 1.1fr 1fr;
    grid-template-rows: auto 1fr auto;
    align-content: start;
  }
  .uvq13-rsz header {
    grid-column: 1/-1;
  }
  .uvq13-rsz .pre {
    grid-row: 2;
    aspect-ratio: auto;
    height: 100%;
  }
  .uvq13-rsz .mt {
    grid-row: 2;
  }
  .uvq13-rsz .act {
    grid-column: 1/-1;
  }
  .uvq13-rsz .fn {
    font-size: 1em;
  }
}

@media (prefers-reduced-motion:reduce) {
  .uvq13-rsz,.uvq13-rsz *,.uvq13-rsz *::before,.uvq13-rsz *::after {
    animation-duration: .01ms!important;
    animation-iteration-count: 1!important;
    transition-duration: .01ms!important;
  }
}
`;

// Interacción en JavaScript del DOM: la misma que en la pestaña JS.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function setup(el: any) {
  const pn=el.querySelector(".pn"),hd=el.querySelector(".hd"),phs=[...el.querySelectorAll(".ph")],D=[7.5,11.5,16];let w=11.5,d=null,cur=null,tms=[];const later=(f,ms)=>tms.push(setTimeout(f,ms));
const isO=()=>el.classList.contains("open");
const setW=v=>{w=v;pn.style.setProperty("--w",v.toFixed(2));const i=D.indexOf(v);if(i>-1)hd.setAttribute("aria-valuenow",String(i))};
const open=(b,u)=>{phs.forEach(x=>x.classList.toggle("on",x===b));cur=b;pn.querySelector(".fn").textContent=b.getAttribute("aria-label");pn.querySelector(".dd").textContent=b.dataset.d;pn.style.setProperty("--a",b.style.getPropertyValue("--a"));pn.style.setProperty("--b",b.style.getPropertyValue("--b"));el.classList.add("open");pn.inert=false;u&&later(()=>hd.focus({preventScroll:true}),350)};
const close=()=>{if(!isO())return;el.classList.remove("open");pn.inert=true;phs.forEach(x=>x.classList.remove("on"));cur&&cur.focus({preventScroll:true});later(()=>!isO()&&setW(11.5),460)};
phs.forEach(b=>b.addEventListener("click",()=>open(b,true)));pn.querySelector(".x").addEventListener("click",close);
hd.addEventListener("pointerdown",e=>{d={x:e.clientX,w,s:el.getBoundingClientRect().width/el.offsetWidth||1,f:parseFloat(getComputedStyle(el).fontSize)};hd.setPointerCapture(e.pointerId);pn.classList.add("dr");el.classList.add("rs")});
hd.addEventListener("pointermove",e=>{if(d)setW(Math.max(3,Math.min(19,d.w-(e.clientX-d.x)/d.s/d.f)))});
const up=()=>{if(!d)return;d=null;pn.classList.remove("dr");el.classList.remove("rs");if(w<5.2){close();return}setW(D.reduce((a,b)=>Math.abs(b-w)<Math.abs(a-w)?b:a))};
hd.addEventListener("pointerup",up);hd.addEventListener("pointercancel",up);
hd.addEventListener("keydown",e=>{const i=D.indexOf(w);if(e.key==="ArrowLeft"){e.preventDefault();setW(D[Math.min(2,i+1)])}else if(e.key==="ArrowRight"){e.preventDefault();i<=0?close():setW(D[i-1])}else if(e.key==="Home"){e.preventDefault();setW(D[2])}else if(e.key==="End"){e.preventDefault();setW(D[0])}});
el.addEventListener("keydown",e=>{if(e.key==="Escape"&&isO()){e.preventDefault();close()}});
pn.inert=true;setW(11.5);later(()=>open(phs[1],false),600);return()=>tms.forEach(clearTimeout)
}

export default function OvResizablePanel() {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const el = ref.current?.firstElementChild;
    return el ? setup(el) : undefined;
  }, []);

  return (
    <>
      <style>{css}</style>
      <div
        ref={ref} style={{
          "--c1": "#db2777",
          "--c2": "#fce7f3",
          "--s": "16px",
          "--k": "1",
          "--fg": "#1d1d1f",
          "--bg": "#f5f5f7",
        } as CSSProperties}
      >
        <div className="uvq13-rsz"><p className="tt">Photos</p><div className="gr"><button type="button" className="ph" style={{ "--a": "#f97316", "--b": "#7c3aed" } as CSSProperties} data-d="4032 × 3024" aria-label="IMG_0412.jpg" aria-haspopup="dialog"></button><button type="button" className="ph" style={{ "--a": "#0ea5e9", "--b": "#14532d" } as CSSProperties} data-d="3024 × 4032" aria-label="IMG_0418.jpg" aria-haspopup="dialog"></button><button type="button" className="ph" style={{ "--a": "#facc15", "--b": "#dc2626" } as CSSProperties} data-d="4032 × 2268" aria-label="IMG_0433.jpg" aria-haspopup="dialog"></button><button type="button" className="ph" style={{ "--a": "#64748b", "--b": "#0f172a" } as CSSProperties} data-d="4032 × 3024" aria-label="IMG_0451.jpg" aria-haspopup="dialog"></button><button type="button" className="ph" style={{ "--a": "#f472b6", "--b": "#0e7490" } as CSSProperties} data-d="3024 × 3024" aria-label="IMG_0460.jpg" aria-haspopup="dialog"></button><button type="button" className="ph" style={{ "--a": "#a3e635", "--b": "#166534" } as CSSProperties} data-d="4032 × 3024" aria-label="IMG_0477.jpg" aria-haspopup="dialog"></button></div><span className="snap" aria-hidden="true"><i style={{ "--d": "7.5" } as CSSProperties}></i><i style={{ "--d": "11.5" } as CSSProperties}></i><i style={{ "--d": "16" } as CSSProperties}></i></span><aside className="pn" role="dialog" aria-labelledby="uvq13-rsz-n"><div className="hd" role="separator" tabIndex={0} aria-orientation="vertical" aria-label="Panel width" aria-valuemin="0" aria-valuemax="2" aria-valuenow="1"><i></i></div><div className="in"><header><b className="fn" id="uvq13-rsz-n"></b><button type="button" className="x" aria-label="Close"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M7 7l10 10M17 7L7 17" /></svg></button></header><i className="pre" aria-hidden="true"></i><dl className="mt"><div><dt>Dimensions</dt><dd className="dd"></dd></div><div><dt>Size</dt><dd>4,2 MB</dd></div><div><dt>Camera</dt><dd>f/1.8 · 1/250</dd></div><div><dt>Date</dt><dd>12/10/2026</dd></div></dl><div className="act"><button type="button"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M12 4v11M7.5 10.5L12 15l4.5-4.5M5 19.5h14" /></svg><span>Download</span></button><button type="button"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M5 7h14M10 11v6M14 11v6M6.5 7l1 12.5h9l1-12.5M9.5 7V4.5h5V7" /></svg><span>Delete</span></button></div></div></aside></div>
      </div>
    </>
  );
}
```

## Prompt for AI assistants

Paste this into ChatGPT, Claude, Cursor, v0 or Lovable to adapt the block to your stack (Tailwind, Vue, Svelte…):

````text
I want to add the “Resizable side panel with snap points” block (Overlays) from AIMRAN Library to my project.

## What it is
A details panel you stretch from its handle that snaps to three widths; its content rearranges itself with container queries.

## Chosen settings
- Main color: #db2777
- Secondary color: #fce7f3
- Size: 16px (from 12 to 24px)
- Speed: 1x (from 0.5 to 2x)
- Text: "Fotos"

## What I need
1. Add it to my project with the same look and animation as the reference code: same sizes, colours, radii, shadows, timings and easing.
2. It is built with plain HTML and CSS (the reference wraps it in a React component with its CSS). If my project uses something else (Vue, Svelte, Astro, plain HTML…), translate it without losing any behaviour. If I use Tailwind, turn the styles into classes; if not, keep the CSS in its own file.
3. Expose the settings above as props (or CSS variables) with those values as defaults.
4. Make it work well on phones: nothing that depends only on hover, touch targets of at least 44 px and no overflow on 320 px screens.
5. Make it look right in light and dark mode.
6. Keep it accessible: semantic HTML, roles and aria-* where needed, visible focus and keyboard support.
7. Respect prefers-reduced-motion: with reduced motion, no big animations but the same end result.

## Reference code
```tsx
// @ts-nocheck: setup() es JavaScript del DOM sin tipos, el mismo de la pestaña JS.
import { useEffect, useRef, type CSSProperties } from "react";

const css = `
.uvq13-rsz {
  font-family: -apple-system,BlinkMacSystemFont,"SF Pro Text","Inter",system-ui,sans-serif;
  font-size: var(--s);
  color: var(--fg);
  -webkit-tap-highlight-color: transparent;
  position: relative;
  width: 22em;
  height: 18em;
  border-radius: 1.2em;
  overflow: hidden;
  background: color-mix(in srgb,var(--fg) 3%,var(--bg));
  box-shadow: 0 0 0 1px color-mix(in srgb,var(--fg) 9%,transparent);
}

.uvq13-rsz .tt {
  margin: 0;
  padding: 1em 1.1em .5em;
  font-weight: 800;
}

.uvq13-rsz .gr {
  display: grid;
  grid-template-columns: repeat(3,1fr);
  gap: .4em;
  padding: 0 1.1em;
}

.uvq13-rsz .ph {
  font: inherit;
  color: inherit;
  border: 0;
  margin: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  aspect-ratio: 1;
  border-radius: .6em;
  background: radial-gradient(circle at 70% 30%,rgba(255,255,255,.5) 0 12%,transparent 13%),linear-gradient(160deg,var(--a),var(--b));
  transition: transform calc(0.2s / var(--k)) cubic-bezier(.34,1.56,.64,1),box-shadow calc(0.2s / var(--k)) cubic-bezier(.25,.1,.25,1);
}

.uvq13-rsz .ph:hover {
  transform: scale(1.03);
}

.uvq13-rsz .ph.on {
  box-shadow: 0 0 0 2px var(--bg),0 0 0 4px var(--c1);
}

.uvq13-rsz .ph:focus-visible {
  outline: 2px solid var(--c1);
  outline-offset: 3px;
}

.uvq13-rsz .snap {
  position: absolute;
  inset: 0;
  z-index: 6;
  pointer-events: none;
  opacity: 0;
  transition: opacity calc(0.2s / var(--k)) cubic-bezier(.25,.1,.25,1);
}

.uvq13-rsz.rs .snap {
  opacity: 1;
}

.uvq13-rsz .snap i {
  position: absolute;
  top: 0;
  bottom: 0;
  right: calc(var(--d) * 1em);
  border-left: 1.5px dashed color-mix(in srgb,var(--c1) 70%,transparent);
}

.uvq13-rsz .pn {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 5;
  width: calc(var(--w,11.5) * 1em);
  box-sizing: border-box;
  container-type: inline-size;
  background: var(--bg);
  box-shadow: -1px 0 0 color-mix(in srgb,var(--fg) 9%,transparent),-1em 0 2em -1em rgba(0,0,0,.35);
  transform: translateX(105%);
  transition: transform calc(0.45s / var(--k)) cubic-bezier(.22,1,.36,1),width calc(0.45s / var(--k)) cubic-bezier(.34,1.56,.64,1);
}

.uvq13-rsz.open .pn {
  transform: none;
}

.uvq13-rsz .pn.dr {
  transition: none;
}

.uvq13-rsz .hd {
  position: absolute;
  left: -.6em;
  top: 0;
  bottom: 0;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 1.2em;
  cursor: ew-resize;
  touch-action: none;
  outline: 0;
}

.uvq13-rsz .hd i {
  width: .32em;
  height: 2.6em;
  border-radius: 1em;
  background: color-mix(in srgb,var(--fg) 22%,transparent);
  transition: background calc(0.2s / var(--k)) cubic-bezier(.25,.1,.25,1),height calc(0.3s / var(--k)) cubic-bezier(.34,1.56,.64,1);
}

.uvq13-rsz .hd:hover i,.uvq13-rsz .dr .hd i,.uvq13-rsz .hd:focus-visible i {
  height: 3.8em;
  background: var(--c1);
}

.uvq13-rsz .hd:focus-visible i {
  box-shadow: 0 0 0 3px color-mix(in srgb,var(--c1) 30%,transparent);
}

.uvq13-rsz .in {
  display: flex;
  flex-direction: column;
  gap: .5em;
  height: 100%;
  padding: .8em .9em .9em 1.1em;
  box-sizing: border-box;
  overflow: hidden;
}

.uvq13-rsz header {
  display: flex;
  align-items: center;
  gap: .4em;
}

.uvq13-rsz .fn {
  flex: 1;
  min-width: 0;
  font-size: .74em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.uvq13-rsz .x {
  font: inherit;
  color: inherit;
  border: 0;
  margin: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  display: grid;
  place-items: center;
  flex: none;
  width: 1.8em;
  height: 1.8em;
  border-radius: 50%;
  background: color-mix(in srgb,var(--fg) 6%,transparent);
}

.uvq13-rsz .x svg {
  width: .9em;
  height: .9em;
}

.uvq13-rsz .pre {
  display: block;
  flex: none;
  aspect-ratio: 16/9;
  border-radius: .6em;
  background: radial-gradient(circle at 70% 30%,rgba(255,255,255,.5) 0 12%,transparent 13%),linear-gradient(160deg,var(--a,#ccc),var(--b,#999));
}

.uvq13-rsz .mt {
  display: none;
  margin: 0;
}

.uvq13-rsz .mt div {
  display: flex;
  justify-content: space-between;
  gap: .5em;
  padding: .3em 0;
  font-size: .66em;
  border-bottom: 1px solid color-mix(in srgb,var(--fg) 9%,transparent);
}

.uvq13-rsz dt {
  color: color-mix(in srgb,var(--fg) 55%,transparent);
}

.uvq13-rsz dd {
  margin: 0;
  font-weight: 650;
  text-align: right;
}

.uvq13-rsz .act {
  display: flex;
  flex-direction: column;
  gap: .35em;
  margin-top: auto;
}

.uvq13-rsz .act button {
  font: inherit;
  color: inherit;
  border: 0;
  margin: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .4em;
  padding: .55em;
  border-radius: .6em;
  font-size: .72em;
  font-weight: 700;
  background: color-mix(in srgb,var(--fg) 6%,transparent);
}

.uvq13-rsz .act button:first-child {
  color: var(--c1);
  background: var(--c2);
}

.uvq13-rsz .act svg {
  width: 1.2em;
  height: 1.2em;
}

.uvq13-rsz .act span {
  display: none;
}

.uvq13-rsz button:focus-visible {
  outline: 2px solid var(--c1);
  outline-offset: 3px;
}

@container (min-width: 10em) {
  .uvq13-rsz .mt {
    display: block;
  }
  .uvq13-rsz .act {
    flex-direction: row;
  }
  .uvq13-rsz .act button {
    flex: 1;
  }
  .uvq13-rsz .act button:first-child span {
    display: inline;
  }
  .uvq13-rsz .fn {
    font-size: .85em;
  }
}

@container (min-width: 14em) {
  .uvq13-rsz .act span {
    display: inline;
  }
  .uvq13-rsz .in {
    display: grid;
    grid-template-columns: 1.1fr 1fr;
    grid-template-rows: auto 1fr auto;
    align-content: start;
  }
  .uvq13-rsz header {
    grid-column: 1/-1;
  }
  .uvq13-rsz .pre {
    grid-row: 2;
    aspect-ratio: auto;
    height: 100%;
  }
  .uvq13-rsz .mt {
    grid-row: 2;
  }
  .uvq13-rsz .act {
    grid-column: 1/-1;
  }
  .uvq13-rsz .fn {
    font-size: 1em;
  }
}

@media (prefers-reduced-motion:reduce) {
  .uvq13-rsz,.uvq13-rsz *,.uvq13-rsz *::before,.uvq13-rsz *::after {
    animation-duration: .01ms!important;
    animation-iteration-count: 1!important;
    transition-duration: .01ms!important;
  }
}
`;

// Interacción en JavaScript del DOM: la misma que en la pestaña JS.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function setup(el: any) {
  const pn=el.querySelector(".pn"),hd=el.querySelector(".hd"),phs=[...el.querySelectorAll(".ph")],D=[7.5,11.5,16];let w=11.5,d=null,cur=null,tms=[];const later=(f,ms)=>tms.push(setTimeout(f,ms));
const isO=()=>el.classList.contains("open");
const setW=v=>{w=v;pn.style.setProperty("--w",v.toFixed(2));const i=D.indexOf(v);if(i>-1)hd.setAttribute("aria-valuenow",String(i))};
const open=(b,u)=>{phs.forEach(x=>x.classList.toggle("on",x===b));cur=b;pn.querySelector(".fn").textContent=b.getAttribute("aria-label");pn.querySelector(".dd").textContent=b.dataset.d;pn.style.setProperty("--a",b.style.getPropertyValue("--a"));pn.style.setProperty("--b",b.style.getPropertyValue("--b"));el.classList.add("open");pn.inert=false;u&&later(()=>hd.focus({preventScroll:true}),350)};
const close=()=>{if(!isO())return;el.classList.remove("open");pn.inert=true;phs.forEach(x=>x.classList.remove("on"));cur&&cur.focus({preventScroll:true});later(()=>!isO()&&setW(11.5),460)};
phs.forEach(b=>b.addEventListener("click",()=>open(b,true)));pn.querySelector(".x").addEventListener("click",close);
hd.addEventListener("pointerdown",e=>{d={x:e.clientX,w,s:el.getBoundingClientRect().width/el.offsetWidth||1,f:parseFloat(getComputedStyle(el).fontSize)};hd.setPointerCapture(e.pointerId);pn.classList.add("dr");el.classList.add("rs")});
hd.addEventListener("pointermove",e=>{if(d)setW(Math.max(3,Math.min(19,d.w-(e.clientX-d.x)/d.s/d.f)))});
const up=()=>{if(!d)return;d=null;pn.classList.remove("dr");el.classList.remove("rs");if(w<5.2){close();return}setW(D.reduce((a,b)=>Math.abs(b-w)<Math.abs(a-w)?b:a))};
hd.addEventListener("pointerup",up);hd.addEventListener("pointercancel",up);
hd.addEventListener("keydown",e=>{const i=D.indexOf(w);if(e.key==="ArrowLeft"){e.preventDefault();setW(D[Math.min(2,i+1)])}else if(e.key==="ArrowRight"){e.preventDefault();i<=0?close():setW(D[i-1])}else if(e.key==="Home"){e.preventDefault();setW(D[2])}else if(e.key==="End"){e.preventDefault();setW(D[0])}});
el.addEventListener("keydown",e=>{if(e.key==="Escape"&&isO()){e.preventDefault();close()}});
pn.inert=true;setW(11.5);later(()=>open(phs[1],false),600);return()=>tms.forEach(clearTimeout)
}

export default function OvResizablePanel() {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const el = ref.current?.firstElementChild;
    return el ? setup(el) : undefined;
  }, []);

  return (
    <>
      <style>{css}</style>
      <div
        ref={ref} style={{
          "--c1": "#db2777",
          "--c2": "#fce7f3",
          "--s": "16px",
          "--k": "1",
          "--fg": "#1d1d1f",
          "--bg": "#f5f5f7",
        } as CSSProperties}
      >
        <div className="uvq13-rsz"><p className="tt">Photos</p><div className="gr"><button type="button" className="ph" style={{ "--a": "#f97316", "--b": "#7c3aed" } as CSSProperties} data-d="4032 × 3024" aria-label="IMG_0412.jpg" aria-haspopup="dialog"></button><button type="button" className="ph" style={{ "--a": "#0ea5e9", "--b": "#14532d" } as CSSProperties} data-d="3024 × 4032" aria-label="IMG_0418.jpg" aria-haspopup="dialog"></button><button type="button" className="ph" style={{ "--a": "#facc15", "--b": "#dc2626" } as CSSProperties} data-d="4032 × 2268" aria-label="IMG_0433.jpg" aria-haspopup="dialog"></button><button type="button" className="ph" style={{ "--a": "#64748b", "--b": "#0f172a" } as CSSProperties} data-d="4032 × 3024" aria-label="IMG_0451.jpg" aria-haspopup="dialog"></button><button type="button" className="ph" style={{ "--a": "#f472b6", "--b": "#0e7490" } as CSSProperties} data-d="3024 × 3024" aria-label="IMG_0460.jpg" aria-haspopup="dialog"></button><button type="button" className="ph" style={{ "--a": "#a3e635", "--b": "#166534" } as CSSProperties} data-d="4032 × 3024" aria-label="IMG_0477.jpg" aria-haspopup="dialog"></button></div><span className="snap" aria-hidden="true"><i style={{ "--d": "7.5" } as CSSProperties}></i><i style={{ "--d": "11.5" } as CSSProperties}></i><i style={{ "--d": "16" } as CSSProperties}></i></span><aside className="pn" role="dialog" aria-labelledby="uvq13-rsz-n"><div className="hd" role="separator" tabIndex={0} aria-orientation="vertical" aria-label="Panel width" aria-valuemin="0" aria-valuemax="2" aria-valuenow="1"><i></i></div><div className="in"><header><b className="fn" id="uvq13-rsz-n"></b><button type="button" className="x" aria-label="Close"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M7 7l10 10M17 7L7 17" /></svg></button></header><i className="pre" aria-hidden="true"></i><dl className="mt"><div><dt>Dimensions</dt><dd className="dd"></dd></div><div><dt>Size</dt><dd>4,2 MB</dd></div><div><dt>Camera</dt><dd>f/1.8 · 1/250</dd></div><div><dt>Date</dt><dd>12/10/2026</dd></div></dl><div className="act"><button type="button"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M12 4v11M7.5 10.5L12 15l4.5-4.5M5 19.5h14" /></svg><span>Download</span></button><button type="button"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M5 7h14M10 11v6M14 11v6M6.5 7l1 12.5h9l1-12.5M9.5 7V4.5h5V7" /></svg><span>Delete</span></button></div></div></aside></div>
      </div>
    </>
  );
}
```

Give me the complete final code, ready to paste, and tell me in one line where to put it in my project.
````
