Skip to content

Instantly share code, notes, and snippets.

@mrclay
Last active May 26, 2026 18:12
Show Gist options
  • Select an option

  • Save mrclay/c0d4a629b6c2de6a4d132968428de264 to your computer and use it in GitHub Desktop.

Select an option

Save mrclay/c0d4a629b6c2de6a4d132968428de264 to your computer and use it in GitHub Desktop.
React/web components to demo a layout at various widths
(() => {
/**
* Provides a centered block container whose max-width can easily
* be set to given CSS lengths via a button click.
*
* Example component demo:
*
* <break-points sizes="1200px,40rem" initSize="40rem">
* ...My component here
* </break-points>
*/
class BreakPointsElement extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
}
connectedCallback() {
this.render();
const initSize = this.getAttribute("init-size") || "";
if (initSize) {
this.shadowRoot.querySelector(".container").style.maxWidth = initSize;
}
}
render() {
const sizesAttr = this.getAttribute("sizes") || "";
const sizes = sizesAttr
.split(",")
.map((s) => s.trim())
.filter(Boolean);
const style = `
:host {
display: block;
}
.buttons {
align-items: center;
display: flex;
flex-wrap: wrap;
font: 14px/17px system-ui, sans-serif;
justify-content: center;
button, label {
align-items: center;
display: flex;
font: inherit;
height: 2rem;
padding: 0 1rem;
cursor: pointer;
background: #f0f0f0;
border: 1px solid #ccc;
border-radius: 4px;
&:hover {
background: #e0e0e0;
}
}
label {
padding-inline: 0.5rem;
}
}
.container {
transition: max-width 0.3s ease;
margin: 0 auto;
max-width: 100%;
&.show-outline {
outline: 1px dashed #f99;
}
&[data-max-width="unset"] {
margin-inline: 2rem;
}
}
`;
this.shadowRoot.innerHTML = `
<style>${style}</style>
<div class="buttons">
<button data-size="unset">unset</button>
${sizes.map((size) => `<button data-size="${size}">${size}</button>`).join("")}
<label><input type="checkbox" id="outline-toggle"> Outline</label>
</div>
<div class="container">
<slot></slot>
</div>
`;
this.shadowRoot.querySelectorAll("button").forEach((btn) => {
btn.addEventListener("click", (e) => {
const width = e.target.getAttribute("data-size");
const div = this.shadowRoot.querySelector(".container");
if (div instanceof HTMLDivElement) {
div.style.maxWidth = width;
div.dataset.maxWidth = width;
}
});
});
this.shadowRoot
.querySelector("#outline-toggle")
.addEventListener("change", (e) => {
const container = this.shadowRoot.querySelector(".container");
container.classList[e.target.checked ? "add" : "remove"](
"show-outline",
);
});
}
}
if (!customElements.get("break-points")) {
customElements.define("break-points", BreakPointsElement);
}
})();
import React, { CSSProperties, useState } from 'react';
interface BreakPointsProps {
/** Comma-separated sizes, e.g., "1200px, 40rem" */
sizes?: string;
/** The initial max-width of the container */
initSize?: string;
children?: React.ReactNode;
}
/**
* Provides a centered block container whose max-width can easily
* be set to given CSS lengths via a button click.
*
* Example component demo:
*
* <BreakPoints sizes="1200px,40rem" initSize="40rem">
* ...My component here
* </BreakPoints>
*/
export const BreakPoints: React.FC<BreakPointsProps> = ({
sizes = '',
initSize = 'unset',
children,
}) => {
const [currentSize, setCurrentSize] = useState(initSize);
const [showOutline, setShowOutline] = useState(false);
const sizeList = sizes
.split(',')
.map((s) => s.trim())
.filter(Boolean);
const hostStyle: React.CSSProperties = {
display: 'block',
};
const styles: Record<string, CSSProperties> = {
buttonsContainerStyle: {
alignItems: 'center',
display: 'flex',
flexWrap: 'wrap',
font: '14px/17px system-ui, sans-serif',
justifyContent: 'center',
gap: '0.5rem',
marginBottom: '1rem',
},
controlStyle: {
alignItems: 'center',
display: 'flex',
font: 'inherit',
height: '2rem',
margin: '0',
padding: '0 1rem',
cursor: 'pointer',
background: '#f0f0f0',
border: '1px solid #ccc',
borderRadius: '4px',
transition: 'background 0.2s ease',
},
containerStyle: {
transition: 'max-width 0.3s ease',
margin: currentSize === 'unset' ? '0 2rem' : '0 auto',
maxWidth: currentSize,
outline: showOutline ? '1px dashed #f99' : 'none',
},
};
const labelStyle: React.CSSProperties = {
...styles.controlStyle,
paddingInline: '0.5rem',
};
return (
<div style={hostStyle}>
{/* Scoped hover styles that inline style objects don't natively support */}
<style>{`
.BreakPoints-control:hover {
background: #e0e0e0 !important;
}
`}</style>
<div style={styles.buttonsContainerStyle}>
<button
className="BreakPoints-control"
style={styles.controlStyle}
onClick={() => setCurrentSize('unset')}
>
unset
</button>
{sizeList.map((size) => (
<button
key={size}
className="BreakPoints-control"
style={styles.controlStyle}
onClick={() => setCurrentSize(size)}
>
{size}
</button>
))}
<label className="BreakPoints-control" style={labelStyle}>
<input
type="checkbox"
checked={showOutline}
onChange={(e) => setShowOutline(e.target.checked)}
style={{ marginRight: '0.5rem', cursor: 'pointer' }}
/>
Outline
</label>
</div>
<div style={styles.containerStyle}>{children}</div>
</div>
);
};
export default BreakPoints;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment