Protohiro Effects builds a React library for hard CSS effects (for example: gradient-border, glow ring, noise overlays, sheen, spotlight) without wrappers and without layout hacks.
Core rules:
- no extra DOM nodes
- no layout shifts
- no runtime layout measurements
- CSS-first, variables-driven
- composable effects
Every effect MUST:
- work on a single existing element
- preserve forwarded refs
- support SSR + hydration
- mutate only
classListand CSS custom properties - clean up classes/variables on unmount
- Effects are hooks, never wrapper components.
- Hook output is a
ref(or ref merger helper), never JSX. - Global CSS for an effect is injected once per app lifetime.
- Runtime options map to CSS variables only.
- No JS animation loops for MVP (
requestAnimationFramedisallowed unless explicitly approved). - No
ResizeObserverunless an effect is impossible without geometry tracking. - Avoid forced sync layout (
offset*,getBoundingClientRect) in render/update paths. - Do not break existing user
className,style, ordata-*attributes.
Each effect must define:
- a unique class namespace (
pe-<effect>) - documented CSS variables (
--pe-...) - defaults that produce a visible but non-invasive output
- cleanup logic for removed variables/classes
- fallback behavior for unsupported CSS features
Canonical structure:
useXEffect(options):
- create/receive target ref
- ensure global CSS is present
- attach effect class
- set effect variables from options
- cleanup on unmount/options change
- return ref
- Respect
border-radius: inheritor equivalent visual clipping. - Must work in light and dark themes.
- Must not require parent wrappers.
- Must not require arbitrary
z-indexchanges unless documented. - Max one pseudo-element per effect (
::beforeor::after) for MVP. - Prefer
@supportsguards for advanced features (mask-composite,conic-gradient, etc.). - If an effect cannot fully degrade, fail gracefully (no broken visuals, no hidden content).
- Chrome: latest 2
- Firefox: latest 2
- Safari: latest 2
- Edge: latest 2
Safari policy:
- add fallback path when behavior differs
- document exact limitation and affected versions
- include visual baseline screenshot for fallback mode
- Option names are semantic (
thickness,radiusMode,intensity,speed), not implementation-specific. - Expose safe defaults; avoid required options unless essential.
- Accept token-friendly values (string/number where reasonable).
- Avoid options that require element measurement for interpretation.
- Keep hook signatures stable and additive.
Run visual + behavior checks on:
buttondiv- card-like component with padding + radius
- element inside
display: flex - element inside
display: grid - element with
overflow: hidden - element with
transform - element with existing pseudo-element styles
- SSR render + hydration
Minimum assertions:
- class attached/removed correctly
- CSS variables updated on option change
- no hydration mismatch warnings
- no console errors in Strict Mode
- One global style injection per effect package load.
- No layout recalculation loops.
- No forced reflow in hot paths.
- No per-frame JS work for visual animation in MVP.
- Bundle increase per new effect should stay within ~1 kB gzip unless justified.
Each PR adding/changing an effect must include:
- demo usage (
button,card, dark mode example) - before/after screenshots (including Safari fallback when applicable)
- browser test notes (Chrome/Firefox/Safari/Edge)
- SSR/hydration verification note
- brief performance note (what changed, why it is safe)
- docs for options + CSS variables + known limitations
- layered/composed effects
- preset packs
- optional motion variants
- theme-driven APIs
- potential framework adapters beyond React
- not a component library
- not a Tailwind replacement
- not a CSS framework
- not a canvas/WebGL renderer (for now)