---
title: "Styling Guide - BYO"
slug: "styling-guide-byo"
tags: ["bring-your-own", "module-builder"]
updated: 2026-07-16T15:50:58Z
published: 2026-07-16T15:50:58Z
canonical: "docs.unqork.io/styling-guide-byo"
---
> ## Documentation Index
> Fetch the complete documentation index at: https://docs.unqork.io/llms.txt
> Use this file to discover all available pages before exploring further.
# Styling Guide - BYO
Learn how to write maintainable, encapsulated styles for BYO custom components.
## Style Encapsulation
### Using Shadow DOM (Recommended)
Shadow DOM provides true style encapsulation—styles inside don't leak out, styles outside don't leak in.
**Project structure**:
```
src/
├── MyButton/
│ ├── MyButton.ts
│ ├── MyButton.css
│ └── index.ts
```
**MyButton.css**:
```css
/* These styles are scoped to this component */
button {
background: #0066cc;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
/* PostCSS nesting support */
&:hover {
background: #0052a3;
}
&:disabled {
opacity: 0.5;
}
}
```
**MyButton.ts**:
```typescript
import styles from './MyButton.css?raw'
// Define styles once, reuse across all instances
const sheet = new CSSStyleSheet()
sheet.replaceSync(styles)
export class MyButton extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'open' })
}
connectedCallback() {
this.shadowRoot!.adoptedStyleSheets = [sheet]
this.shadowRoot!.innerHTML = ``
}
}
```
> **Note**: Using PostCSS with nesting and other features requires configuration. See [CSS Framework Support](/docs/styling-guide-byo#css-framework-support) section below.
**Benefits**:
- ✅ No class name collisions with platform or other components.
- ✅ Can use simple, semantic selectors without needing BEM.
- ✅ Styles are bundled with the component.
- ✅ Prevents accidental style pollution.
**Considerations**:
- Global styles like fonts and CSS variables don't automatically inherit.
- Some CSS frameworks might not work as expected.
- Requires understanding of Shadow DOM APIs.
### Without Shadow DOM (Platform Styles)
If you need to integrate with platform styles, use namespaced BEM class names to prevent collisions. See [BEM Class Naming](/docs/styling-guide-byo#bem-class-naming) for detailed guidance.
**Platform styles**:
```css
.acme-task-list {
display: flex;
flex-direction: column;
gap: 8px;
/* Inherits platform fonts and CSS variables */
font-family: var(--platform-font-family, inherit);
}
.acme-task-list__item {
display: flex;
align-items: center;
padding: 12px;
}
.acme-task-list__title {
flex: 1;
}
.acme-task-list__item--completed .acme-task-list__title {
text-decoration: line-through;
opacity: 0.6;
}
```
**TaskList.ts**:
```typescript
export class TaskList extends HTMLElement {
private _items: Task[] = []
connectedCallback() {
this.render()
}
render() {
this.innerHTML = `
${this._items.map(item => `
${item.title}
`).join('')}
`
}
}
```
> **Note**: The platform is responsible for loading `TaskList.css`. The component only renders markup with BEM class names.
**Why use platform styles**:
- ✅ Inherits platform fonts, colors, and CSS variables automatically.
- ✅ Integrates with the global design system.
- ✅ CSS file is separate and easier to maintain.
- ✅ Can leverage platform utility classes.
- ✅ Works with all CSS frameworks and tooling.
**Implications of not using Shadow DOM**:
| Platform Styles (no Shadow DOM) | Shadow DOM |
| --- | --- |
| ✅ Inherits platform fonts, CSS variables, themes | ⚠️ Must explicitly inherit or redefine |
| ✅ Can use platform utility classes | ❌ Cannot access global styles |
| ✅ Integrates with global design system | ❌ Fully isolated |
| ❌ Global styles can interfere with your component | ✅ Styles are isolated |
| ❌ Your styles can leak to other components | ✅ No style leakage |
| ❌ Must use BEM/namespacing to prevent collisions | ✅ Can use simple selectors |
| ✅ Works with all CSS frameworks | ⚠️ Some frameworks might not penetrate the shadow boundary |
## BEM Class Naming
When not using Shadow DOM, use BEM (Block Element Modifier) methodology to write maintainable, collision-free class names.
### What Is BEM?
BEM is a naming convention that makes class names predictable and self-documenting. It structures class names into three parts:
```
.block__element--modifier
```
- **Block**: Standalone component (`.card`, `.button`, `.task-list`).
- **Element**: Part of a block (`.card__header`, `.button__icon`, `.task-list__item`).
- **Modifier**: Variation or state (`.card--featured`, `.button--disabled`, `.task-list__item--completed`).
### Syntax Rules
**Block**:
```css
.block-name { }
```
- Describes what it is (`.menu`, `.search-form`, `.product-card`).
- Use lowercase and hyphens for multi-word names.
- No prefixes except namespace.
**Element**:
```css
.block-name__element-name { }
```
- Describes its role in the block.
- Double underscore `__` separates block from element.
- Cannot exist outside the block.
```css
/* ✅ Good */
.task-list__item { }
.task-list__title { }
.task-list__checkbox { }
/* ❌ Bad - element without block */
.item { }
.title { }
```
**Modifier**:
```css
.block-name--modifier-name { }
.block-name__element-name--modifier-name { }
```
- Describes state, variation, or behavior.
- Double hyphen `--` separates from block/element.
- Applied alongside the base class.
```html
```
### Best Practices
**Namespace your components (Required)**:
Always prefix your BYO component class names with your company or project name to prevent collisions with platform styles or other BYO components. This is not optional—every BYO component should be namespaced.
```css
/* ✅ Good - company/project namespace */
.acme-task-list { }
.acme-task-list__item { }
.acme-task-list__item--completed { }
.mycompany-button { }
.mycompany-button__icon { }
.mycompany-button--primary { }
/* ❌ Bad - no namespace, will collide */
.task-list { }
.button { }
```
Recommended namespace patterns, listed in priority order:
1. **Company name**: `.acme-*`, `.mycompany-*`, `.contoso-*`
2. **Project name**: `.myproject-*`, `.client-portal-*`
3. **Feature area**: `.analytics-*`, `.workflow-*`, `.reporting-*`
4. **Component library**: `.ui-*`, `.core-*`, `.custom-*`
**Why namespacing is critical**:
- ✅ Prevents conflicts with Unqork platform styles.
- ✅ Prevents conflicts with other BYO components.
- ✅ Makes ownership clear in browser DevTools.
- ✅ Enables safe global CSS updates without breaking your components.
- ✅ Follows industry best practices for third-party components.
**Keep selectors flat**:
```css
/* ✅ Good - flat BEM selectors */
.acme-card { }
.acme-card__header { }
.acme-card__title { }
.acme-card__body { }
.acme-card--featured { }
/* ❌ Bad - nested selectors */
.acme-card .header { }
.acme-card .header .title { }
.acme-card.featured { }
```
**Don't chain elements**:
```css
/* ✅ Good */
.acme-card__header { }
.acme-card__header-title { }
/* ❌ Bad - no .block__element__element */
.acme-card__header__title { }
```
If you need deeper nesting, create a new element name that describes the full context.
**Use meaningful names**:
```css
/* ✅ Good - describes purpose */
.acme-product-card__price { }
.acme-product-card__add-button { }
.acme-product-card--on-sale { }
/* ❌ Bad - generic or positional */
.acme-product-card__text { }
.acme-product-card__button-1 { }
.acme-product-card--blue { }
```
**Avoid presentation-based names**:
```css
/* ✅ Good - semantic */
.alert--warning { }
.button--primary { }
.text--error { }
/* ❌ Bad - presentational */
.alert--yellow { }
.button--blue { }
.text--red { }
```
semantic names survive design changes; presentational names don't.
### Common Patterns
**State modifiers**:
```css
.acme-button { }
.acme-button--disabled { }
.acme-button--loading { }
.acme-button--active { }
```
```html
```
**Variant modifiers**:
```css
.acme-button { }
.acme-button--primary { }
.acme-button--secondary { }
.acme-button--ghost { }
```
```html
```
**Size modifiers**:
```css
.acme-button { }
.acme-button--small { }
.acme-button--large { }
```
```html
```
**Combining modifiers**:
```html
```
### When to Use BEM
**Use BEM when**:
- Not using Shadow DOM to allow platform style inheritance.
- Styles might conflict with platform or other components.
- Building reusable component libraries.
- Need predictable, maintainable class names.
**Don't use BEM when**:
- Using Shadow DOM, where simple selectors work fine.
- Writing utility classes (`.hidden`, `.text-center`).
- One-off styles that won't be reused.
### Examples
**Task list component with namespace**:
```css
/* Block */
.acme-task-list {
display: flex;
flex-direction: column;
gap: 8px;
}
/* Elements */
.acme-task-list__item {
display: flex;
align-items: center;
padding: 12px;
background: white;
border: 1px solid #ddd;
}
.acme-task-list__checkbox {
margin-right: 12px;
}
.acme-task-list__title {
flex: 1;
}
.acme-task-list__delete-button {
margin-left: auto;
}
/* Modifiers */
.acme-task-list__item--completed {
opacity: 0.6;
}
.acme-task-list__item--completed .acme-task-list__title {
text-decoration: line-through;
}
.acme-task-list__delete-button--disabled {
pointer-events: none;
opacity: 0.3;
}
```
**Component markup**:
```typescript
export class TaskList extends HTMLElement {
render() {
this.innerHTML = `
${this._items.map(item => `
${item.title}
`).join('')}
`
}
}
```
## CSS Framework Support
### PostCSS (Recommended)
[PostCSS](https://postcss.org) transforms your CSS at build time, enabling modern CSS features like imports, nesting, and autoprefixing. It's the recommended approach for sharing design tokens across component stylesheets.
**Features**:
- ✅ **CSS imports**: Share tokens with `@import`.
- ✅ **Nesting**: Write nested selectors like Sass/LESS.
- ✅ **Autoprefixer**: Vendor prefixes added automatically.
- ✅ **No runtime overhead**: All transformations happen at build time.
- ✅ **Works with Shadow DOM**: Processed CSS works everywhere.
**Installation**:
```bash
npm install -D postcss-import postcss-nesting autoprefixer
```
**Configuration**:
```typescript
// vite.config.ts
import { defineConfig } from 'vite'
import postcssImport from 'postcss-import'
import postcssNesting from 'postcss-nesting'
import autoprefixer from 'autoprefixer'
export default defineConfig({
css: {
postcss: {
plugins: [postcssImport(), postcssNesting(), autoprefixer()],
},
},
})
```
> **See [Sharing Styles Across Components](/docs/styling-guide-byo#sharing-styles-across-components)** for detailed examples of using PostCSS to share design tokens.
### Comparison: PostCSS vs. Plain CSS
| Feature | PostCSS | Plain CSS |
| --- | --- | --- |
| **Setup complexity** | Medium (requires config) | None |
| **Build-time processing** | ✅ Yes | ❌ No |
| **Runtime overhead** | ❌ None | ❌ None |
| **Dynamic styles** | ❌ No | ⚠️ Limited (CSS variables) |
| **CSS imports/composition** | ✅ Yes (@import) | ❌ No (with ?raw) |
| **Nesting** | ✅ Yes | ❌ No |
| **Autoprefixing** | ✅ Yes | ❌ No |
| **Unique class names** | ❌ Manual (BEM) | ❌ Manual (BEM) |
| **Bundle size impact** | Medium (inlined imports) | Smallest |
| **Learning curve** | Low | None |
| **Best for** | Sharing tokens, modern CSS features | Simple static styles |
**Decision guide**:
- **Use Plain CSS** when:
- The component has simple, static styles.
- No need to share styles across components.
- Want the smallest bundle size.
- Prefer a standard CSS workflow.
- **Use PostCSS** when:
- Need to share design tokens across components.
- Want modern CSS features like nesting and imports.
- Need autoprefixing for browser compatibility.
- Styles are static but benefit from composition.
## Sharing Styles Across Components
With `?raw` imports, you can't use CSS `@import` in the traditional way. Here are practical approaches to share design tokens and common styles across multiple BYO components.
### Approach 1: PostCSS (Recommended)
[PostCSS](https://postcss.org) transforms your CSS at build time, enabling features like imports, custom properties, nesting, and autoprefixing. Use it to share design tokens across component stylesheets.
**Project structure**:
```
src/
├── tokens.css
├── Button/
│ ├── Button.ts
│ ├── Button.css
│ └── index.ts
├── Card/
│ ├── Card.ts
│ ├── Card.css
│ └── index.ts
└── entry.ts
```
**tokens.css**:
```css
/* Global design tokens */
:root {
--color-primary: #0066cc;
--color-surface: white;
--color-text: #212529;
--spacing-sm: 8px;
--spacing-md: 16px;
--radius: 4px;
}
```
**Button.css**:
```css
@import './tokens.css';
button {
background: var(--color-primary);
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--radius);
color: white;
border: none;
/* Nesting support with postcss-nesting */
&:hover {
opacity: 0.9;
}
}
```
**Button.ts**:
```typescript
import styles from './Button.css?raw'
const sheet = new CSSStyleSheet()
sheet.replaceSync(styles)
export class Button extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'open' })
}
connectedCallback() {
this.shadowRoot!.adoptedStyleSheets = [sheet]
this.shadowRoot!.innerHTML = ``
}
}
```
> **Note**: PostCSS installation and configuration is shown in the [CSS Framework Support](/docs/styling-guide-byo#css-framework-support) section above.
After processing, `Button.css?raw` contains the inlined tokens, prefixed CSS, and expanded nesting.
**Benefits**:
- ✅ **Shared tokens**: Import tokens into every component stylesheet at build time.
- ✅ **CSS imports**: Compose styles from multiple files.
- ✅ **Nesting**: Write nested selectors like in Sass/LESS.
- ✅ **Autoprefixer**: Vendor prefixes added automatically.
- ✅ **No runtime overhead**: All transformations happen at build time.
- ✅ **Works with Shadow DOM**: Processed CSS works everywhere.
- ✅ **Standard CSS output**: Final output is pure CSS.
**Tradeoffs**:
- ❌ Requires build configuration.
- ❌ Tokens duplicated in each component bundle.
- ❌ No dynamic styles—everything is static at build time.
### Approach 2: JavaScript Token Constants
Export design tokens as JavaScript constants for use in CSS-in-JS or template literals.
**tokens.js or tokens.ts**:
```javascript
export const tokens = {
color: {
primary: '#0066cc',
surface: 'white',
text: '#212529',
},
spacing: {
sm: '8px',
md: '16px',
},
radius: '4px',
}
```
**Button.js using tokens**:
```javascript
import { tokens } from '../tokens'
const styles = new CSSStyleSheet()
styles.replaceSync(`
button {
background: ${tokens.color.primary};
padding: ${tokens.spacing.sm} ${tokens.spacing.md};
border-radius: ${tokens.radius};
color: white;
border: none;
}
`)
export class Button extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'open' })
}
connectedCallback() {
this.shadowRoot!.adoptedStyleSheets = [styles]
this.shadowRoot!.innerHTML = ``
}
}
```
**Benefits**:
- ✅ Works with JavaScript or TypeScript.
- ✅ Autocomplete in IDE using TypeScript or JSDoc.
- ✅ Can compute values dynamically.
- ✅ Shareable across components.
- ✅ Type-safe when using TypeScript.
### Comparison
| Approach | Shadow DOM | Type Safety | Bundle Size | Dynamic Styles | Use Case |
| --- | --- | --- | --- | --- | --- |
| PostCSS | ✅ Yes | ❌ No | Medium | ❌ No | Shared tokens, nesting, autoprefixing |
| JavaScript Constants | ✅ Yes | ⚠️ Optional | Small | ✅ Yes | Shared values, computed styles, dynamic styling |
**Recommended approach**:
- Use **PostCSS** to share design tokens across component stylesheets.
- Use **JavaScript constants** when you need type safety or dynamic computed styles.
- Combine both for maximum flexibility.
## Summary
**Takeaways**:
- **Shadow DOM**: Provides true style encapsulation so styles cannot leak in or out.
- **Platform styles**: Use a separate .css file with BEM naming to integrate with global platform styles.
- **PostCSS**: Share design tokens and use modern CSS features like imports, nesting, and autoprefixing across component stylesheets.
- **Constructable Stylesheets**: Improve performance when using external CSS files in Shadow DOM.
**Decision Matrix**:
| Use Case | Recommended Approach |
| --- | --- |
| New isolated component | Shadow DOM + Constructable Stylesheets |
| Need platform style inheritance | Platform Styles (separate .css + BEM naming) |
| Share design tokens across components | PostCSS with @import |
| Modern CSS features (nesting, autoprefixer) | PostCSS |
| Dynamic styles based on component state | CSS Variables + JavaScript |
| Large stylesheet (Shadow DOM) | External .css file with Constructable Stylesheets |
| Large stylesheet (Light DOM) | Platform Styles (separate .css + BEM) |
**Next Steps**:
- Review [Best Practices](/unqorkai/docs/best-practices-byo) for broader guidance.
- Check [Building Components](/unqorkai/docs/building-components-byo-sdk) for implementation details.
- See [FAQ](/unqorkai/docs/faq-byo) for common questions.
---
**Changelog**
| Date | Change |
| --- | --- |
| 2026-05-15 | Initial publication |
The Shadow DOM is a web standard that allows you to create encapsulated, hidden DOM subtrees for your web components. It provides a way to scope CSS styles and JavaScript behavior within a component, preventing them from leaking out and interfering with the rest of the page.
Cascading Style Sheets (CSS) is a style sheet language used for presenting how a HTML or XML document looks to end-users.
The Document Object Model (DOM) is the data representation of objects that form the structure and content of a document on the web. The DOM represents the page.
APIs (application programming interfaces) are a set of protocols and definitions developers use to build and integrate application software. APIs act as the connective tissue between products and services.
The DevTools Console helps you securely store, build, test, and deploy your software.
Semantic HTML uses HTML elements to clearly describe their meaning, rather than just their appearance, to structure web content for better readability and accessibility for both humans and machines.
JavaScript is an object-oriented computer programming language. It is most-commonly used for interactive effects in the browser.
The browser-based workspace where Creators build, edit, review, and manage Unqork applications. Also referred to as the Unqork platform. The IDE replaces what was previously referred to as UDesigner and Classic Designer, which are considered legacy.