
Open Props UI is a PURE CSS component library that uses next-generation CSS features (like container queries, CSS layers, and OKLCH color spaces) to create highly customizable components you can copy and paste directly into your project.
The library takes a refreshing approach to component distribution. Instead of importing a black-box library or NPM package, you take the provided HTML and CSS and adapt it.
Features:
- Copy-paste architecture – No npm dependencies for components, just copy the code you need
- Modern CSS foundation – Built with CSS Grid, container queries, CSS layers, and OKLCH colors
- Open Props integration – Uses the Open Props design token system for consistency
- Accessibility-first design – Follows WCAG guidelines with proper focus management and semantic markup
- Framework agnostic – Works with any framework or vanilla HTML
How to use it:
1. Install the necessary Open Props package.
# NPM $ npm install opbeta@npm:open-props@VERSION -S # PNPM $ pnpm install opbeta@npm:open-props@VERSION -S
2. Create a main.css file to define the structure and import the necessary 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"; @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/tab-buttons.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 "./inputs/range.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";
3. You’ll also need to grab the normalize.css, utils.css, and theme.css files from the Open Props UI repository.
/* normalize.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;
}
}
/* Utils */
@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;
}
}
}
}
}
/* Theme */
@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)
);
}
}
4. Browse the component registry, then copy both HTML and CSS for the components you need. All available components:
- Button
- Button group
- Icon Button
- Tab buttons
- Toggle button group
- Checkbox
- Radio
- Range
- Select
- Switch
- Textarea
- Text field
- Accordion
- Avatar
- Badge
- Card
- Chip
- Definition list
- Divider
- List
- Table
- Alert
- Dialog
- Progress
- Snackbar
- Spinner
- Rich text
- Typography
FAQs:
Q: What browsers support these components?
A: Most components work in modern browsers (Chrome 90+, Firefox 88+, Safari 14+), but some experimental features require Chrome with feature flags enabled.
Q: Can I use this in production applications?
A: Use caution with experimental features in production. The base components using stable CSS features are production-ready, but always test thoroughly in your target browsers.
Q: How do I customize component styles?
A: Since you own the CSS code, you can modify anything directly. The theme system provides CSS custom properties for common customizations, or you can edit the component CSS files directly.
Q: How do I handle browser fallbacks?
A: The library includes some fallbacks through @supports queries, but you need to add additional fallback styles for your specific browser support requirements. Consider progressive enhancement strategies for critical features.
The post Copy-Paste CSS Components Library for Modern Web – Open Props UI appeared first on CSS Script.
Discover more from RSS Feeds Cloud
Subscribe to get the latest posts sent to your email.
