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
This guide will set you up with:
- Open Props
- a normalize
- some util classes
- base theme 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 i open-props@2.0.0-beta.5 --save-alias opbeta
# npm
npm i open-props@2.0.0-beta.5 --save-alias opbeta
# yarn
yarn add open-props@2.0.0-beta.5 --alias opbeta
2. 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.
Setup files
@layer openprops, normalize, utils, theme, components.base, components.has-deps;
/*
* Open Props
* Import as many props as you need here.
* https://unpkg.com/browse/open-props@2.0.0-beta.5/css
*/
@import "opbeta/css/media-queries.css" layer(openprops);
@import "opbeta/index.css" layer(openprops);
@import "opbeta/css/sizes/media.css" layer(openprops);
@import "opbeta/css/font/lineheight.css" layer(openprops);
@import "opbeta/css/color/hues.oklch.css" layer(openprops);
/* Normalize */
@import "./normalize.css";
/* Utils */
@import "./utils.css";
/* Theme */
@import "./theme.css";
/*
* Components
* Components are divided into two categories - if they are stand-alone (base) or if they have dependencies (has-deps).
*/
/*** Base components (no dependencies) */
@import "./actions/button.css";
@import "./actions/icon-button.css";
@import "./actions/toggle-button-group.css";
@import "./data-display/avatar.css";
@import "./data-display/badge.css";
@import "./data-display/card.css";
@import "./data-display/chip.css";
@import "./data-display/definition-list.css";
@import "./data-display/divider.css";
@import "./data-display/link.css";
@import "./data-display/table.css";
@import "./feedback/progress.css";
@import "./feedback/spinner.css";
@import "./inputs/checkbox-radio.css";
@import "./inputs/switch.css";
@import "./text/typography.css";
/*** Has dependencies */
@import "./actions/button-group.css";
@import "./data-display/accordion.css";
@import "./data-display/list.css";
@import "./feedback/alert.css";
@import "./feedback/dialog.css";
@import "./feedback/snackbar.css";
@import "./inputs/field-group.css";
@import "./inputs/field.css";
@import "./inputs/select.css";
@import "./inputs/text-field.css";
@import "./inputs/textarea.css";
@import "./text/rich-text.css";
@layer normalize {
*,
:before,
::after {
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;
}
}
}
}
}
@layer theme {
html {
color-scheme: var(--color-scheme);
}
.light {
--color-scheme: light;
}
.dark {
--color-scheme: dark;
}
:where(html) {
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)
);
}
}
Folder structure
This is folder structure that comes out of the box. Feel free to change it to your needs.
├─ main.css
├─ normalize.css
├─ theme.css
├─ utils.css
├─ actions
│ └─ ...
├─ data-display
│ └─ ...
├─ feedback
│ └─ ...
├─ inputs
│ └─ ...
└─ text
└─ ...
While many components will work as intended in modern browsers - some are using features that haven't landed outside Chromium with experimental feature flags enabled.
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!