diff --git a/.changeset/kind-crabs-march.md b/.changeset/kind-crabs-march.md
new file mode 100644
index 000000000..cd1131dc5
--- /dev/null
+++ b/.changeset/kind-crabs-march.md
@@ -0,0 +1,5 @@
+---
+"@atomicjolt/atomic-elements": minor
+---
+
+Wrap all styles in a @layer elements directive for easier style overrides
diff --git a/packages/atomic-elements/docs/Guides/Customization/CSSLayers.mdx b/packages/atomic-elements/docs/Guides/Customization/CSSLayers.mdx
new file mode 100644
index 000000000..446da5f92
--- /dev/null
+++ b/packages/atomic-elements/docs/Guides/Customization/CSSLayers.mdx
@@ -0,0 +1,104 @@
+# CSS Layers
+
+Atomic Elements supports wrapping all component styles in a [CSS `@layer`](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer),
+giving you explicit control over how library styles interact with your own CSS.
+
+## Why use CSS layers?
+
+Without layers, overriding library styles requires matching or beating their specificity — which often means
+adding extra selectors, using `!important`, or relying on source order. CSS layers solve this cleanly:
+styles in a lower-priority layer are always overridden by styles outside any layer, regardless of specificity.
+
+```css
+/* Without layers: this might not win due to specificity */
+.my-button {
+ background: blue;
+}
+
+/* With layers: this always wins, no specificity tricks needed */
+@layer elements {
+ .aje-btn {
+ background: red;
+ }
+}
+.my-button {
+ background: blue;
+} /* beats @layer elements unconditionally */
+```
+
+## Enabling layers
+
+By default, `ElementsProvider` wraps all component styles in an `@layer elements` block.
+No configuration is needed — just use `ElementsProvider` as normal:
+
+```tsx
+import { ElementsProvider } from "@atomicjolt/atomic-elements";
+
+const App = () => (
+
+
+
+);
+```
+
+## Customizing the layer name
+
+Use the `layerName` prop to change the layer name. This is useful when you need to coordinate
+layer ordering with other style sources:
+
+```tsx
+
+
+
+```
+
+## Controlling layer order
+
+Declare your layer stack at the top of your global CSS to lock in the cascade order:
+
+```css
+/* Establish layer order — last layer wins */
+@layer reset, elements, overrides;
+```
+
+With this in place, any styles you write outside a layer (or inside `@layer overrides`) will
+always take precedence over `@layer elements`, making it straightforward to customize
+component styles without fighting specificity.
+
+```css
+@layer reset, elements, overrides;
+
+@layer overrides {
+ /* These always win over @layer elements, no !important needed */
+ .aje-btn--primary {
+ --btn-bg-clr: indigo;
+ --btn-hover-bg-clr: darkslateblue;
+ }
+}
+```
+
+## Overriding with plain CSS
+
+Styles written outside any `@layer` have higher priority than any layered style by default.
+This means you can override component styles with ordinary CSS rules, even ones with lower specificity:
+
+```css
+/* No @layer declaration needed — unlayered styles always win */
+.my-button {
+ --btn-bg-clr: green;
+}
+```
+
+## Browser support
+
+`@layer` is supported in all modern browsers (Chrome 99+, Firefox 97+, Safari 15.4+).
+For applications that need to support older browsers, the layer wrapping will cause all
+component styles to be ignored in those browsers, since unsupported at-rules are skipped.
+If you need to support older browsers, do not use the `layerName` prop — disable the feature
+by passing null or an empty string:
+
+```tsx
+
+
+
+```
diff --git a/packages/atomic-elements/src/components/ElementsProvider.tsx b/packages/atomic-elements/src/components/ElementsProvider.tsx
index 10c7e6a5f..edb5f0fed 100644
--- a/packages/atomic-elements/src/components/ElementsProvider.tsx
+++ b/packages/atomic-elements/src/components/ElementsProvider.tsx
@@ -1,16 +1,22 @@
-import { createContext, useContext } from "react";
-import { ThemeProvider } from "styled-components";
+import { createContext, useContext, useMemo } from "react";
+import { StyleSheetManager, ThemeProvider } from "styled-components";
import { CssGlobalDefaults } from "@styles/globals";
import { defaultTheme, Theme } from "@styles/theme";
+import { layerPlugin } from "@styles/plugins";
export interface ElementsConfig {
/** The theme to be used by the application. */
theme?: Theme;
- /**Flag to determine if default styles should be applied.
+ /** Flag to determine if default styles should be applied.
* NOTE: this will apply some styles globally to your page,
* so only use this if you are not using a global style reset already.
*/
applyDefaultStyles?: boolean;
+
+ /** The name of the CSS layer to use for the components' styles.
+ * @default "elements"
+ */
+ layerName?: string | null;
}
export interface ElementsProviderProps extends ElementsConfig {
@@ -33,16 +39,36 @@ export function useElementsConfig(): ElementsConfig {
* wrap the root of the application and provides the theme and global styles
*/
export function ElementsProvider(props: ElementsProviderProps) {
- const { children, theme = defaultTheme, applyDefaultStyles = false } = props;
+ const {
+ children,
+ theme = defaultTheme,
+ applyDefaultStyles = false,
+ layerName = "elements",
+ } = props;
+
const CssVariables = theme._Component;
+ const plugins = useMemo(() => {
+ const plugins = [];
+
+ if (layerName) {
+ plugins.push(layerPlugin({ name: layerName }));
+ }
+
+ return plugins;
+ }, [layerName]);
+
return (
-
-
-
- {applyDefaultStyles && }
- {children}
-
-
+
+
+
+
+ {applyDefaultStyles && }
+ {children}
+
+
+
);
}
diff --git a/packages/atomic-elements/src/styles/plugins.ts b/packages/atomic-elements/src/styles/plugins.ts
new file mode 100644
index 000000000..20a5030b6
--- /dev/null
+++ b/packages/atomic-elements/src/styles/plugins.ts
@@ -0,0 +1,37 @@
+import { Element, Middleware, serialize } from "stylis";
+
+interface LayerPluginOptions {
+ name?: string;
+}
+
+export function layerPlugin(options: LayerPluginOptions = {}): Middleware {
+ const { name = "elements" } = options;
+
+ return (element, _index, _children, callback) => {
+ if (element.type !== "rule" || element.return) return;
+
+ // Skip rules inside @layer (leave alone) or @keyframes (not real CSS rules)
+ let parent = element.parent;
+ while (parent) {
+ if (parent.type === "@layer" || parent.type === "@keyframes") return;
+ parent = parent.parent;
+ }
+
+ const selector = Array.isArray(element.props)
+ ? element.props.join(",")
+ : element.props;
+
+ // element.children can be a string if element is a declaration,
+ // but it should always be an array for rules
+ const declarations = serialize(element.children as Element[], callback);
+
+ // Clear children so that middlewares down the line don't overwrite
+ // element.return with their own stringification of the rule.
+ element.children = [];
+
+ // Set element.return so styled-components' rulesheet collects our output.
+ // (styled-components ignores serialize()'s return value — it reads element.return instead.)
+ // Also return it directly for non-rulesheet usage.
+ return (element.return = `@layer ${name}{${selector}{${declarations}}}`);
+ };
+}