Open Props UI is just CSS. Therefore you're able to go and copy and paste any component right now without any installs. You don't even need Open Props! It won't necessarily look and feel as intended, but if that suits your project then skip this page.
Guide
Getting started
You can choose between:
- Manual setup (more fun, recommended)
- NPM installation (a lot less fun)
- CDN (not fun either but good for quick prototyping)
Manual setup
1. Install Open Props
Open Props v2 hasn't dropped yet, which is why this project relies on the OPv2 beta. The difference right now between the OPv1 and the OPv2 beta aren't huge (the way OPUI consumes it), so you should be fine with either version.
# pnpm
pnpm add opbeta@npm:open-props@2.0.0-beta.5 -S
# npm
npm i opbeta@npm:open-props@2.0.0-beta.5 -S2. Base setup
The setup process will differ a bit if you use a framework, but the core principles still apply. You should have no problems getting it to work though 👍 Otherwise, let me know.
This is a nice, recommended folder structure. Feel free to change it to your needs.
├─ src
└─ core
│ └─ normalize.css
│ └─ utils.css
└─ [theme folder name]
└─ theme.css
└─ open-props.css
└─ components
└─ button.css
└─ ...imports.css
imports all your CSSopen-props.css
consists of Open Props imports, onlytheme.css
OPUI theme setup
@layer openprops, normalize, theme, components.root, components.extended, utils;
/* Open Props */
@import "../../open-props.css";
/* Normalize */
@import "../../core/normalize.css";
/* Theme */
@import "./theme.css";
/* Components */
@import "./components.css";
/* Utils */
@import "../../core/utils.css";/*
* Open Props
* Import as many props as you need here.
* https://unpkg.com/browse/open-props@2.0.0-beta.5/css
*/
@import "open-props/css/media-queries.css";
@import "open-props/index.css" layer(openprops);
@import "open-props/css/sizes/media.css" layer(openprops);
@import "open-props/css/font/lineheight.css" layer(openprops);
@import "open-props/hues.oklch.css" layer(openprops);/*
theme one theme setup
*/
@layer theme {
.light {
--color-scheme: light;
}
.dark {
--color-scheme: dark;
}
:where(html) {
color-scheme: var(--color-scheme, light dark);
--palette-hue: var(--oklch-teal);
--palette-hue-rotate-by: 5;
--palette-chroma: 0.89;
/* Primary */
--primary: var(--color-8);
--primary-light: oklch(from var(--primary) calc(l * 1.25) c h);
--primary-dark: oklch(from var(--primary) calc(l * 0.75) c h);
--primary-contrast: var(--gray-1);
/* Text */
--text-color-1: light-dark(var(--gray-15), var(--gray-1));
--text-color-1-contrast: light-dark(var(--gray-2), var(--gray-15));
--text-color-2: light-dark(var(--gray-13), var(--gray-4));
--text-color-2-contrast: light-dark(var(--gray-4), var(--gray-13));
/* Surface */
--surface-default: light-dark(var(--gray-1), var(--gray-13));
--surface-filled: light-dark(var(--gray-3), var(--gray-15));
--surface-tonal: light-dark(var(--gray-3), var(--gray-12));
--surface-elevated: light-dark(var(--gray-1), var(--gray-12));
/* Shadows */
--shadow-color: light-dark(220 3% 15%, 220 40% 2%);
--shadow-strength: light-dark(1%, 10%);
--inner-shadow-highlight: light-dark(
inset 0 -0.5px 0 0 #fff,
inset 0 0.5px 0 0 #0001,
inset 0 -0.5px 0 0 #fff1,
inset 0 0.5px 0 0 #0007
);
/* Typography */
--font-size-h1: var(--font-size-fluid-3, 3.5rem);
--font-size-h2: var(--font-size-fluid-2, 2rem);
--font-size-h3: var(--font-size-fluid-1, 1.5rem);
--font-size-h4: var(--font-size-3, 1.25rem);
--font-size-h5: var(--font-size-2, 1.1rem);
--font-size-h6: var(--font-size-fluid-0, 1rem);
--font-size-lg: var(--font-size-3, 1.25rem);
--font-size-md: var(--font-size-fluid-0, 1rem);
--font-size-sm: 0.875rem;
--font-size-xs: var(--font-size-0, 0.75rem);
/* Borders */
--border-color: light-dark(var(--gray-4), var(--gray-12));
--border-radius: var(--size-1);
--border-width: 1px;
/* Input Field */
--field-border-color: var(--border-color);
--field-border-radius: var(--size-1);
--field-border-width: 1px;
--field-size: 2.3lh;
--field-size-small: 1.9lh;
/* Button */
--button-border-radius: var(--radius-round);
/* Ripple effect */
@media (prefers-reduced-motion: no-preference) {
--button-ripple-size: 100%;
--button-ripple-duration: 0.5s;
}
}
/* Highlight colors */
:where(.red, .error, del) {
--palette-hue: var(--oklch-red, 25);
--palette-chroma: 1;
--palette-hue-rotate-by: 1;
}
:where(.blue, .ok, abbr, dfn) {
--palette-hue: var(--oklch-blue, 210);
--palette-chroma: 1;
--palette-hue-rotate-by: 1;
}
:where(.green, .good, ins) {
--palette-hue: var(--oklch-green, 145);
--palette-chroma: 1;
--palette-hue-rotate-by: 1;
}
:where(.orange, .warning) {
--palette-hue: var(--oklch-orange, 75);
--palette-chroma: 1;
--palette-hue-rotate-by: 1;
}
:where(html) {
--red: oklch(from var(--color-9) l 0.2 25);
--blue: oklch(from var(--color-9) l 0.2 210);
--green: oklch(from var(--color-9) l 0.2 145);
--orange: oklch(from var(--color-7) l 0.2 75);
}
/* Gray palette */
:where(html) {
--gray-chroma: 0.01;
--gray-lightness: 255;
--gray-1: oklch(
from var(--color-1) l var(--gray-chroma) var(--gray-lightness)
);
--gray-2: oklch(
from var(--color-2) l var(--gray-chroma) var(--gray-lightness)
);
--gray-3: oklch(
from var(--color-3) l var(--gray-chroma) var(--gray-lightness)
);
--gray-4: oklch(
from var(--color-4) l var(--gray-chroma) var(--gray-lightness)
);
--gray-5: oklch(
from var(--color-5) l var(--gray-chroma) var(--gray-lightness)
);
--gray-6: oklch(
from var(--color-6) l var(--gray-chroma) var(--gray-lightness)
);
--gray-7: oklch(
from var(--color-7) l var(--gray-chroma) var(--gray-lightness)
);
--gray-8: oklch(
from var(--color-8) l var(--gray-chroma) var(--gray-lightness)
);
--gray-9: oklch(
from var(--color-9) l var(--gray-chroma) var(--gray-lightness)
);
--gray-10: oklch(
from var(--color-10) l var(--gray-chroma) var(--gray-lightness)
);
--gray-11: oklch(
from var(--color-11) l var(--gray-chroma) var(--gray-lightness)
);
--gray-12: oklch(
from var(--color-12) l var(--gray-chroma) var(--gray-lightness)
);
--gray-13: oklch(
from var(--color-13) l var(--gray-chroma) var(--gray-lightness)
);
--gray-14: oklch(
from var(--color-14) l var(--gray-chroma) var(--gray-lightness)
);
--gray-15: oklch(
from var(--color-15) l var(--gray-chroma) var(--gray-lightness)
);
--gray-16: oklch(
from var(--color-16) l var(--gray-chroma) var(--gray-lightness)
);
}
}@layer openprops, normalize, theme, components.root, components.extended, utils;
/* Open Props */
@import "../../open-props.css";
/* Normalize */
@import "../../core/normalize.css";
/* Theme */
@import "./theme.css";
/* Components */
@import "./components.css";
/* Utils */
@import "../../core/utils.css";/*
theme two theme setup
*/
@layer theme {
.light {
--color-scheme: light;
}
.dark {
--color-scheme: dark;
}
:where(html) {
color-scheme: var(--color-scheme, light dark);
--palette-hue: var(--oklch-orange);
--palette-hue-rotate-by: 0;
--palette-chroma: 0.89;
/* Primary */
--primary: var(--color-8);
--primary-light: oklch(from var(--primary) calc(l * 1.25) c h);
--primary-dark: oklch(from var(--primary) calc(l * 0.75) c h);
--primary-contrast: var(--gray-1);
/* Text */
--text-color-1: light-dark(var(--gray-15), var(--gray-1));
--text-color-1-contrast: light-dark(var(--gray-2), var(--gray-15));
--text-color-2: light-dark(var(--gray-13), var(--gray-4));
--text-color-2-contrast: light-dark(var(--gray-4), var(--gray-13));
/* Surface */
--surface-default: light-dark(var(--gray-1), var(--gray-13));
--surface-filled: light-dark(var(--gray-3), var(--gray-15));
--surface-tonal: light-dark(var(--gray-3), var(--gray-12));
--surface-elevated: light-dark(var(--gray-1), var(--gray-12));
/* Shadows */
--shadow-color: light-dark(220 3% 15%, 220 40% 2%);
--shadow-strength: light-dark(1%, 10%);
--inner-shadow-highlight: light-dark(
inset 0 -0.5px 0 0 #fff,
inset 0 0.5px 0 0 #0001,
inset 0 -0.5px 0 0 #fff1,
inset 0 0.5px 0 0 #0007
);
/* Typography */
--font-size-h1: var(--font-size-fluid-3, 3.5rem);
--font-size-h2: var(--font-size-fluid-2, 2rem);
--font-size-h3: var(--font-size-fluid-1, 1.5rem);
--font-size-h4: var(--font-size-3, 1.25rem);
--font-size-h5: var(--font-size-2, 1.1rem);
--font-size-h6: var(--font-size-fluid-0, 1rem);
--font-size-lg: var(--font-size-3, 1.25rem);
--font-size-md: var(--font-size-fluid-0, 1rem);
--font-size-sm: 0.875rem;
--font-size-xs: var(--font-size-0, 0.75rem);
/* Borders */
--border-color: light-dark(var(--gray-4), var(--gray-12));
--border-radius: var(--size-2);
--border-width: 1px;
/* Input Field */
--field-border-color: var(--border-color);
--field-border-radius: var(--size-2);
--field-border-width: 1px;
--field-size: 2.3lh;
--field-size-small: 1.9lh;
/* Button */
--button-border-radius: var(--size-2);
}
/* Highlight colors */
:where(.red, .error, del) {
--palette-hue: var(--oklch-red, 25);
--palette-chroma: 1;
--palette-hue-rotate-by: 1;
}
:where(.blue, .ok, abbr, dfn) {
--palette-hue: var(--oklch-blue, 210);
--palette-chroma: 1;
--palette-hue-rotate-by: 1;
}
:where(.green, .good, ins) {
--palette-hue: var(--oklch-green, 145);
--palette-chroma: 1;
--palette-hue-rotate-by: 1;
}
:where(.orange, .warning) {
--palette-hue: var(--oklch-orange, 75);
--palette-chroma: 1;
--palette-hue-rotate-by: 1;
}
:where(html) {
--red: oklch(from var(--color-9) l 0.2 25);
--blue: oklch(from var(--color-9) l 0.2 210);
--green: oklch(from var(--color-9) l 0.2 145);
--orange: oklch(from var(--color-7) l 0.2 75);
}
/* Gray palette */
:where(html) {
--gray-chroma: 0.01;
--gray-lightness: 255;
--gray-1: oklch(
from var(--color-1) l var(--gray-chroma) var(--gray-lightness)
);
--gray-2: oklch(
from var(--color-2) l var(--gray-chroma) var(--gray-lightness)
);
--gray-3: oklch(
from var(--color-3) l var(--gray-chroma) var(--gray-lightness)
);
--gray-4: oklch(
from var(--color-4) l var(--gray-chroma) var(--gray-lightness)
);
--gray-5: oklch(
from var(--color-5) l var(--gray-chroma) var(--gray-lightness)
);
--gray-6: oklch(
from var(--color-6) l var(--gray-chroma) var(--gray-lightness)
);
--gray-7: oklch(
from var(--color-7) l var(--gray-chroma) var(--gray-lightness)
);
--gray-8: oklch(
from var(--color-8) l var(--gray-chroma) var(--gray-lightness)
);
--gray-9: oklch(
from var(--color-9) l var(--gray-chroma) var(--gray-lightness)
);
--gray-10: oklch(
from var(--color-10) l var(--gray-chroma) var(--gray-lightness)
);
--gray-11: oklch(
from var(--color-11) l var(--gray-chroma) var(--gray-lightness)
);
--gray-12: oklch(
from var(--color-12) l var(--gray-chroma) var(--gray-lightness)
);
--gray-13: oklch(
from var(--color-13) l var(--gray-chroma) var(--gray-lightness)
);
--gray-14: oklch(
from var(--color-14) l var(--gray-chroma) var(--gray-lightness)
);
--gray-15: oklch(
from var(--color-15) l var(--gray-chroma) var(--gray-lightness)
);
--gray-16: oklch(
from var(--color-16) l var(--gray-chroma) var(--gray-lightness)
);
}
}/*
* Open Props
* Import as many props as you need here.
* https://unpkg.com/browse/open-props@2.0.0-beta.5/css
*/
@import "open-props/css/media-queries.css";
@import "open-props/index.css" layer(openprops);
@import "open-props/css/sizes/media.css" layer(openprops);
@import "open-props/css/font/lineheight.css" layer(openprops);
@import "open-props/hues.oklch.css" layer(openprops);src/core
@layer normalize {
*,
::before,
::after {
/* Set box sizing instead of inheriting */
/* https://www.oddbird.net/2025/09/04/box-model/ */
box-sizing: border-box;
}
* {
scrollbar-width: thin;
}
:where(html) {
--_page-bg-color: var(--surface-default);
accent-color: var(--primary);
background-color: var(--_page-bg-color);
block-size: 100%;
caret-color: var(--primary);
color: var(--text-color-2);
font-family: var(--font-sans);
interpolate-size: allow-keywords;
line-height: var(--font-lineheight-4);
/* https://kilianvalkhof.com/2022/css-html/your-css-reset-needs-text-size-adjust-probably/ */
-moz-text-size-adjust: none;
-webkit-text-size-adjust: none;
text-size-adjust: none;
@media (--motionOK) {
scroll-behavior: smooth;
}
}
:where(body) {
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
container-type: inline-size;
font-size: 16px;
font-synthesis: style;
font-weight: 400;
inline-size: 100%;
margin: 0;
min-block-size: 100%;
min-inline-size: 320px;
position: relative;
text-rendering: optimizeLegibility;
}
/* TODO */
:where(:not(dialog, popover)) {
margin: 0;
}
:where(:not(fieldset, progress, meter)) {
background-origin: border-box;
background-repeat: no-repeat;
border-style: solid;
border-width: 0;
}
:where(fieldset) {
border: var(--field-border-width) solid var(--field-border-color);
border-radius: var(--field-border-radius);
padding: var(--size-3);
display: grid;
gap: var(--size-3);
}
:where(input, button, textarea),
:where(input[type="file"])::-webkit-file-upload-button {
color: inherit;
font-size: inherit;
font: inherit;
letter-spacing: inherit;
}
:where(input):-webkit-autofill,
:where(input):-webkit-autofill:hover,
:where(input):-webkit-autofill:focus,
:where(textarea):-webkit-autofill,
:where(textarea):-webkit-autofill:hover,
:where(textarea):-webkit-autofill:focus,
:where(select):-webkit-autofill,
:where(select):-webkit-autofill:hover,
:where(select):-webkit-autofill:focus,
:where(input):autofill,
:where(input):autofill:hover,
:where(input):autofill:focus,
:where(textarea):autofill,
:where(textarea):autofill:hover,
:where(textarea):autofill:focus,
:where(select):autofill,
:where(select):autofill:hover,
:where(select):autofill:focus {
-webkit-text-fill-color: var(--text-color-2);
-webkit-box-shadow: 0 0 0px 1e5px var(--well-1) inset;
transition: background-color 5000s ease-in-out 0s;
}
::placeholder {
color: var(--text-color-2);
}
::-moz-placeholder {
opacity: 1;
}
:focus-visible {
/* Inverts the --_page-bg-color */
--_focus-visible-color: rgb(
from var(--_page-bg-color) calc(255 - r) calc(255 - g) calc(255 - b)
);
border-radius: var(--border-radius, 0px);
outline: 2px solid var(--_focus-visible-color);
outline-offset: 2px;
}
@media (--motionOK) {
:where(:focus-visible) {
transition: outline-offset 145ms var(--ease-2);
}
:where(:not(:active):focus-visible) {
transition-duration: 0.15s;
}
}
:where(:not(:active):focus-visible) {
outline-offset: var(--outline-offset, 0px);
}
:where(
a[href],
area,
button,
input:not(
[type="text"],
[type="email"],
[type="number"],
[type="password"],
[type=""],
[type="tel"],
[type="url"]
),
label[for],
select,
summary
) {
cursor: pointer;
}
:where(
a[href],
area,
button,
[role="button"],
input,
label[for],
select,
summary,
textarea,
[tabindex]:not([tabindex*="-"])
) {
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
}
:where(img, svg, video, canvas, audio, iframe, embed, object) {
display: block;
}
:where(img, svg, video) {
block-size: auto;
max-inline-size: 100%;
}
:where(svg:not([width])) {
inline-size: var(--size-7);
}
:where(dt:not(:first-of-type)) {
margin-block-start: var(--size-5);
}
:where(figure) {
display: grid;
gap: var(--size-2);
place-items: center;
}
:target {
scroll-margin-block-start: 2rem;
}
}@layer utils {
/*
Screen-reader only
When you visibly want to hide an element but make it accessible for screen readers.
*/
.sr-only {
block-size: 1px;
clip-path: inset(50%);
inline-size: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
}
/* Hover and active effect for checkbox, radio and icon buttons */
:where(.checkbox input, .radio input, .icon-button) {
--isLTR: 1;
--isRTL: -1;
position: relative;
transform-style: preserve-3d;
&:dir(rtl) {
--isLTR: -1;
--isRTL: 1;
}
&:where(:not([disabled])) {
&:hover:before {
--thumb-scale: 1;
}
&:active:before {
--thumb-scale: 1.1;
}
&::before {
--thumb-scale: 0.01;
--highlight-size: 150%;
background-color: oklch(0.6 0 0 / 0.2);
block-size: var(--highlight-size);
clip-path: circle(50%);
content: "";
inline-size: var(--highlight-size);
inset-block-start: 50%;
inset-inline-start: 50%;
position: absolute;
transform-origin: center center;
transform: translateX(calc(var(--isRTL) * 50%)) translateY(-50%)
translateZ(-1px) scale(var(--thumb-scale));
will-change: transform;
@media (prefers-reduced-motion: no-preference) {
transition: transform 0.2s ease;
}
}
}
}
}3. Copy & paste
Browse all the components.
Copy and paste the HTML and CSS (see the "Installation" section on each component page) and you're good to go!
NPM installation
# pnpm
pnpm add opui-css -S
# npm
npm i opui-css -SThen check your node_modules folder for the opui-css package and pick and choose what files you want to use!
Bundled files
dist/theme-one.css
OP, imports, theme, componentsdist/theme-two.css
OP, imports, theme, componentsdist/op.css
all the needed Open Props imports