CSS Integration with JavaScript and Frameworks

1. CSS Object Model (CSSOM) and DOM Manipulation

API Method/Property Usage Example
element.style Inline style manipulation Set/get inline styles directly el.style.color = 'red';
getComputedStyle() Get computed values Read final CSS values after cascade getComputedStyle(el).fontSize
classList add, remove, toggle, contains Manage CSS classes programmatically el.classList.add('active')
CSSStyleSheet insertRule, deleteRule Dynamically modify stylesheets sheet.insertRule('.new { }')
document.styleSheets Access all stylesheets Iterate through all CSS files document.styleSheets[0]
CSS.supports() Feature detection Check if browser supports CSS property CSS.supports('display', 'grid')
matchMedia() Media query matching Listen to media query changes in JS matchMedia('(min-width: 768px)')
CSS Typed OM CSSUnitValue, CSSStyleValue Typed CSS values in JS (Houdini) el.attributeStyleMap.set('width', CSS.px(100))

Example: Manipulation Examples

<!-- 1. Direct style manipulation -->
const box = document.querySelector('.box');

// Set individual properties
box.style.backgroundColor = '#3498db';
box.style.width = '200px';
box.style.transform = 'translateX(50px)';

// Set multiple properties with cssText
box.style.cssText = 'background: red; width: 200px; height: 200px;';

// Read computed styles
const computedStyle = getComputedStyle(box);
console.log(computedStyle.width); // "200px" (computed, not always same as set)
console.log(computedStyle.backgroundColor); // "rgb(52, 152, 219)"

<!-- 2. Class manipulation (preferred over direct style) -->
box.classList.add('active', 'highlighted');
box.classList.remove('inactive');
box.classList.toggle('visible');
if (box.classList.contains('active')) { /* ... */ }

<!-- 3. Dynamic stylesheet creation -->
const sheet = new CSSStyleSheet();
sheet.insertRule(`
  .dynamic-class {
    color: white;
    background: linear-gradient(45deg, #667eea, #764ba2);
    padding: 1rem;
  }
`);
document.adoptedStyleSheets = [sheet];

<!-- 4. CSS feature detection -->
if (CSS.supports('display', 'grid')) {
  document.body.classList.add('grid-supported');
} else {
  document.body.classList.add('flexbox-fallback');
}

// Check property-value pair
if (CSS.supports('backdrop-filter', 'blur(10px)')) {
  // Use backdrop-filter
}

<!-- 5. Media query listeners -->
const mediaQuery = window.matchMedia('(min-width: 768px)');

function handleMediaChange(e) {
  if (e.matches) {
    console.log('Tablet/Desktop view');
    document.body.classList.add('large-screen');
  } else {
    console.log('Mobile view');
    document.body.classList.remove('large-screen');
  }
}

mediaQuery.addEventListener('change', handleMediaChange);
handleMediaChange(mediaQuery); // Initial check

Example: CSS Typed OM (Houdini) API

<!-- Modern typed CSS values API -->
const element = document.querySelector('.box');

// Old way: String manipulation
element.style.width = (parseInt(element.style.width) + 10) + 'px';

// New way: Typed values
const currentWidth = element.attributeStyleMap.get('width');
element.attributeStyleMap.set('width', CSS.px(currentWidth.value + 10));

// Math operations
element.attributeStyleMap.set('width', CSS.calc(CSS.percent(50), CSS.px(20)));

// Transform manipulation
element.attributeStyleMap.set('transform', new CSSTransformValue([
  new CSSTranslate(CSS.px(100), CSS.px(50)),
  new CSSRotate(CSS.deg(45))
]));

// Read computed values
const computedStyleMap = element.computedStyleMap();
const fontSize = computedStyleMap.get('font-size'); // CSSUnitValue {value: 16, unit: "px"}
console.log(fontSize.value); // 16
console.log(fontSize.unit); // "px"
Performance Considerations:
  • Avoid style thrashing: Batch reads (getComputedStyle) before writes (style.property)
  • Use classes over inline styles: classList changes trigger single style recalc vs multiple for inline
  • Debounce/throttle: Limit style updates in scroll/resize handlers
  • RequestAnimationFrame: Schedule style changes before next paint for smooth animations
  • CSS containment: Use contain property to isolate style recalculation scope

2. CSS-in-JS Implementation Patterns

Library Approach Pros Cons
styled-components Tagged templates, runtime Dynamic styles, theming, SSR, TypeScript Runtime overhead, bundle size, learning curve
Emotion CSS prop, runtime/compile Flexible API, composition, SSR, fast Runtime cost, requires babel plugin
Vanilla Extract Zero-runtime, build-time TypeScript-first, no runtime, type-safe Less flexible, static styles only
Linaria Zero-runtime, build-time No runtime cost, standard CSS Limited dynamic styles, build step required
Stitches Near-zero runtime Variants, theming, tiny runtime, TypeScript New library, smaller ecosystem
Panda CSS Build-time, atomic CSS Zero runtime, type-safe, utility-first Requires build step, new ecosystem

Example: styled-components Pattern

// Installation: npm install styled-components
import styled from 'styled-components';

// Basic styled component
const Button = styled.button`
  background: ${props => props.primary ? '#3498db' : '#ecf0f1'};
  color: ${props => props.primary ? 'white' : '#2c3e50'};
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 4px;
  font-size: 1rem;
  cursor: pointer;
  transition: all 0.3s ease;

  &:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  }

  &:disabled {
    opacity: 0.6;
    cursor: not-allowed;
  }
`;

// Usage
<Button primary>Primary Button</Button>
<Button>Secondary Button</Button>

// Extending styles
const IconButton = styled(Button)`
  padding: 0.5rem;
  border-radius: 50%;
`;

// Theming
import { ThemeProvider } from 'styled-components';

const theme = {
  colors: {
    primary: '#3498db',
    secondary: '#2ecc71',
    text: '#2c3e50'
  },
  spacing: {
    sm: '0.5rem',
    md: '1rem',
    lg: '2rem'
  }
};

const ThemedButton = styled.button`
  background: ${props => props.theme.colors.primary};
  padding: ${props => props.theme.spacing.md};
  color: white;
`;

<ThemeProvider theme={theme}>
  <ThemedButton>Themed Button</ThemedButton>
</ThemeProvider>

Example: Emotion CSS Prop Pattern

// Installation: npm install @emotion/react
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';

// Object styles
const buttonStyle = {
  backgroundColor: '#3498db',
  color: 'white',
  padding: '0.75rem 1.5rem',
  borderRadius: '4px',
  border: 'none',
  cursor: 'pointer',
  '&:hover': {
    backgroundColor: '#2980b9'
  }
};

// Template literal styles
const containerStyle = css`
  display: flex;
  gap: 1rem;
  padding: 2rem;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
`;

// Usage with css prop
function MyComponent() {
  return (
    <div css={containerStyle}>
      <button css={buttonStyle}>Click me</button>
      
      {/* Inline styles */}
      <span css={{
        fontSize: '1.2rem',
        fontWeight: 'bold',
        color: 'white'
      }}>
        Text
      </span>
    </div>
  );
}

// Composition
const baseButton = css`
  padding: 0.75rem 1.5rem;
  border: none;
  cursor: pointer;
`;

const primaryButton = css`
  ${baseButton}
  background: #3498db;
  color: white;
`;

Example: Vanilla Extract (Zero-runtime)

// styles.css.ts (build-time processed)
import { style, createTheme } from '@vanilla-extract/css';

// Type-safe theme
export const [themeClass, vars] = createTheme({
  color: {
    primary: '#3498db',
    secondary: '#2ecc71',
    text: '#2c3e50'
  },
  spacing: {
    small: '0.5rem',
    medium: '1rem',
    large: '2rem'
  }
});

// Type-safe styles
export const button = style({
  backgroundColor: vars.color.primary,
  color: 'white',
  padding: vars.spacing.medium,
  borderRadius: '4px',
  border: 'none',
  cursor: 'pointer',
  ':hover': {
    backgroundColor: vars.color.secondary
  }
});

// Component.tsx
import * as styles from './styles.css';

function Button() {
  return (
    <div className={styles.themeClass}>
      <button className={styles.button}>
        Click me
      </button>
    </div>
  );
}

// Result: Static CSS file generated at build time, zero runtime
CSS-in-JS Trade-offs:
  • ⚠️ Runtime cost: styled-components/Emotion add 10-20KB + runtime style injection
  • ⚠️ Server-side rendering: Requires additional setup for SSR to avoid FOUC
  • ⚠️ DevTools: Harder to debug with generated class names (use displayName)
  • ⚠️ Build complexity: Zero-runtime solutions need build configuration
  • When to use: Complex dynamic theming, component libraries, type-safe styles
  • When to avoid: Simple static sites, performance-critical apps, marketing pages

3. CSS Modules and PostCSS Integration

Tool Purpose Key Features
CSS Modules Local scoping Automatic class name hashing, composition, explicit dependencies
PostCSS CSS transformation Plugin ecosystem, autoprefixer, future CSS syntax
postcss-preset-env Future CSS today Nesting, custom properties, custom media queries
Autoprefixer Vendor prefixes Automatic -webkit/-moz/-ms prefixes based on browserslist
cssnano CSS minification Compression, optimization, duplicate removal

Example: CSS Modules Implementation

<!-- Button.module.css -->
.button {
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.3s ease;
}

.primary {
  composes: button;
  background: #3498db;
  color: white;
}

.secondary {
  composes: button;
  background: #ecf0f1;
  color: #2c3e50;
}

.large {
  padding: 1rem 2rem;
  font-size: 1.2rem;
}

<!-- Button.jsx -->
import styles from './Button.module.css';

function Button({ variant = 'primary', size, children }) {
  const classNames = [
    styles[variant],
    size === 'large' && styles.large
  ].filter(Boolean).join(' ');
  
  return <button className={classNames}>{children}</button>;
}

<Button variant="primary" size="large">Click me</Button>

<!-- Generated HTML -->
<button class="Button_primary__a1b2c Button_large__d3e4f">Click me</button>

<!-- Benefits -->
✅ No naming conflicts (hashed class names)
✅ Explicit dependencies (import/export)
✅ Dead code elimination (unused styles removed)
✅ Composition with 'composes' keyword
Example: PostCSS Configuration
<!-- postcss.config.js -->
module.exports = {
  plugins: [
    // 1. Enable future CSS syntax
    require('postcss-preset-env')({
      stage: 3,
      features: {
        'nesting-rules': true,
        'custom-properties': true,
        'custom-media-queries': true,
        'custom-selectors': true
      }
    }),
    
    // 2. Add vendor prefixes
    require('autoprefixer')({
      overrideBrowserslist: [
        'last 2 versions',
        '> 1%',
        'not dead',
        'not ie 11'
      ]
    }),
    
    // 3. Optimize for production
    process.env.NODE_ENV === 'production' && require('cssnano')({
      preset: ['default', {
        discardComments: { removeAll: true },
        normalizeWhitespace: true,
        minifyFontValues: true,
        convertValues: true
      }]
    })
  ].filter(Boolean)
};

<!-- Input CSS (future syntax) -->
:root {
  --color-primary: #3498db;
}

@custom-media --tablet (min-width: 768px);

.container {
  background: var(--color-primary);
  
  & .title {
    font-size: 2rem;
    
    &:hover {
      color: red;
    }
  }
  
  @media (--tablet) {
    padding: 2rem;
  }
}

<!-- Output CSS (browser-compatible) -->
:root {
  --color-primary: #3498db;
}

.container {
  background: var(--color-primary);
}

.container .title {
  font-size: 2rem;
}

.container .title:hover {
  color: red;
}

@media (min-width: 768px) {
  .container {
    padding: 2rem;
  }
}
PostCSS Plugin Recommendations:
  • postcss-import: Inline @import rules (like Sass imports)
  • postcss-nested: Sass-like nesting syntax
  • postcss-custom-properties: Compile CSS variables for IE11
  • postcss-flexbugs-fixes: Fix flexbox browser bugs automatically
  • postcss-normalize: Use parts of normalize.css based on browserslist
  • stylelint: Lint CSS during PostCSS build process

4. Web Components and Shadow DOM Styling

Styling Method Scope Use Case
<style> in Shadow DOM Component-scoped Encapsulated styles, no global leakage
:host selector Style component itself Outer container styling of custom element
:host-context() Conditional styling Style based on ancestor elements
::slotted() Projected content Style content passed via <slot>
::part() External styling Expose internal elements for styling (CSS Shadow Parts)
Constructable Stylesheets Shared styles Reuse stylesheets across multiple shadow roots
CSS Custom Properties Inherit through shadow Theming, customizable component properties

Example: Web Component with Shadow DOM Styling

<!-- Define custom element -->
class FancyButton extends HTMLElement {
  constructor() {
    super();
    
    // Create shadow root
    this.attachShadow({ mode: 'open' });
    
    // Add styles
    this.shadowRoot.innerHTML = `
      <style>
        /* :host targets the component itself (<fancy-button>) */
        :host {
          display: inline-block;
          --button-bg: #3498db;
          --button-color: white;
        }
        
        /* :host with state */
        :host([disabled]) {
          opacity: 0.6;
          pointer-events: none;
        }
        
        /* :host-context styles based on ancestors */
        :host-context(.dark-theme) {
          --button-bg: #2c3e50;
        }
        
        /* Internal styles (encapsulated) */
        button {
          background: var(--button-bg);
          color: var(--button-color);
          padding: 0.75rem 1.5rem;
          border: none;
          border-radius: 4px;
          cursor: pointer;
          font-family: inherit;
          font-size: 1rem;
          transition: transform 0.2s;
        }
        
        button:hover {
          transform: translateY(-2px);
        }
        
        /* Style slotted content */
        ::slotted(svg) {
          width: 1rem;
          height: 1rem;
          margin-right: 0.5rem;
        }
      </style>
      
      <button part="button">
        <slot name="icon"></slot>
        <slot>Click me</slot>
      </button>
    `;
  }
}

customElements.define('fancy-button', FancyButton);

<!-- Usage -->
<fancy-button>Regular Button</fancy-button>
<fancy-button disabled>Disabled Button</fancy-button>

<fancy-button>
  <svg slot="icon">...</svg>
  With Icon
</fancy-button>

<!-- Style from outside using CSS Shadow Parts -->
<style>
  fancy-button::part(button) {
    border-radius: 8px;
    box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  }
  
  /* Customize with CSS variables */
  fancy-button {
    --button-bg: #2ecc71;
    --button-color: white;
  }
</style>

Example: Constructable Stylesheets (Reusable Styles)

<!-- Create shared stylesheet -->
const sharedStyles = new CSSStyleSheet();
sharedStyles.replaceSync(`
  * {
    box-sizing: border-box;
  }
  
  :host {
    display: block;
    font-family: system-ui, -apple-system, sans-serif;
  }
  
  button {
    cursor: pointer;
    transition: all 0.2s;
  }
`);

// Apply to multiple shadow roots
class ComponentA extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.adoptedStyleSheets = [sharedStyles];
    shadow.innerHTML = `<button>Component A</button>`;
  }
}

class ComponentB extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.adoptedStyleSheets = [sharedStyles];
    shadow.innerHTML = `<button>Component B</button>`;
  }
}

// Benefits: Styles are shared in memory, no duplication
Shadow DOM Styling Best Practices:
  • Use CSS Custom Properties for theming (they inherit through shadow boundaries)
  • Expose ::part() pseudo-elements for external customization of internal elements
  • Use Constructable Stylesheets to share common styles across components
  • Keep styles minimal - shadow DOM already provides encapsulation
  • Use :host-context() sparingly - prefer CSS variables for theming
  • Test in browsers that don't support Shadow DOM (polyfills may be needed)

5. Framework-specific CSS Solutions

Framework CSS Solution Approach Example
React CSS Modules, styled-components, Tailwind className prop, css prop, utility classes <div className={styles.container}>
Vue Scoped styles, CSS Modules <style scoped> in SFC <style scoped>.container { }</style>
Angular ViewEncapsulation, global styles Emulated, ShadowDOM, None styleUrls: ['./component.css']
Svelte Scoped by default <style> in component (auto-scoped) <style>.button { }</style>
Next.js CSS Modules, Tailwind, styled-jsx Built-in CSS Modules, global CSS import styles from './page.module.css'
Astro Scoped styles, global styles <style> scoped by default <style>.title { }</style>

Example: React CSS Patterns

<!-- 1. CSS Modules -->
// Button.module.css
.button {
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 4px;
}

.primary { background: #3498db; color: white; }
.secondary { background: #ecf0f1; color: #2c3e50; }

// Button.jsx
import styles from './Button.module.css';
export default function Button({ variant = 'primary', children }) {
  return (
    <button className={`${styles.button} ${styles[variant]}`}>
      {children}
    </button>
  );
}

<!-- 2. Tailwind CSS -->
export default function Button({ variant = 'primary', children }) {
  const baseClasses = 'px-6 py-3 rounded border-none cursor-pointer';
  const variantClasses = {
    primary: 'bg-blue-500 text-white hover:bg-blue-600',
    secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300'
  };
  
  return (
    <button className={`${baseClasses} ${variantClasses[variant]}`}>
      {children}
    </button>
  );
}

<!-- 3. Styled-components -->
import styled from 'styled-components';

const StyledButton = styled.button`
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 4px;
  background: ${props => props.variant === 'primary' ? '#3498db' : '#ecf0f1'};
  color: ${props => props.variant === 'primary' ? 'white' : '#2c3e50'};
`;

export default function Button({ variant = 'primary', children }) {
  return <StyledButton variant={variant}>{children}</StyledButton>;
}

Example: Vue Scoped Styles

<!-- Button.vue -->
<template>
  <button :class="['button', variant]">
    <slot></slot>
  </button>
</template>

<script setup>
defineProps({
  variant: {
    type: String,
    default: 'primary'
  }
});
</script>

<style scoped>
.button {
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.3s ease;
}

.primary {
  background: #3498db;
  color: white;
}

.primary:hover {
  background: #2980b9;
}

.secondary {
  background: #ecf0f1;
  color: #2c3e50;
}

/* Deep selector for child components */
:deep(.icon) {
  margin-right: 0.5rem;
}

/* Global selector */
:global(.dark-mode) .button {
  border: 1px solid rgba(255,255,255,0.2);
}
</style>

<!-- CSS Modules in Vue -->
<style module>
.button {
  padding: 0.75rem 1.5rem;
}
</style>

<template>
  <button :class="$style.button">Click</button>
</template>

Example: Angular ViewEncapsulation

// button.component.ts
import { Component, Input, ViewEncapsulation } from '@angular/core';

@Component({
  selector: 'app-button',
  template: `
    <button [class]="'button ' + variant">
      <ng-content></ng-content>
    </button>
  `,
  styleUrls: ['./button.component.css'],
  encapsulation: ViewEncapsulation.Emulated // Default
  // Options:
  // - Emulated: Scoped styles with attribute selectors (default)
  // - ShadowDom: Native Shadow DOM encapsulation
  // - None: Global styles (no encapsulation)
})
export class ButtonComponent {
  @Input() variant: string = 'primary';
}

// button.component.css
.button {
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 4px;
}

.primary {
  background: #3498db;
  color: white;
}

/* :host selector for component itself */
:host {
  display: inline-block;
}

:host(.large) .button {
  padding: 1rem 2rem;
  font-size: 1.2rem;
}

/* ::ng-deep for piercing encapsulation (deprecated) */
::ng-deep .icon {
  margin-right: 0.5rem;
}

Example: Svelte Scoped Styles

<!-- Button.svelte -->
<script>
  export let variant = 'primary';
</script>

<button class="button {variant}">
  <slot></slot>
</button>

<style>
  /* Automatically scoped - no manual work needed */
  .button {
    padding: 0.75rem 1.5rem;
    border: none;
    border-radius: 4px;
    cursor: pointer;
  }
  
  .primary {
    background: #3498db;
    color: white;
  }
  
  .secondary {
    background: #ecf0f1;
    color: #2c3e50;
  }
  
  /* :global() for global styles */
  :global(body.dark-mode) .button {
    border: 1px solid rgba(255,255,255,0.2);
  }
  
  /* Dynamic styles with variables */
  .button {
    --button-bg: #3498db;
    background: var(--button-bg);
  }
</style>

<!-- Usage -->
<Button variant="primary">Click me</Button>

Framework CSS Solution Comparison:

Consideration Best Choice Reason
Zero config scoping Vue, Svelte, Angular Built-in scoped styles, no extra setup
Dynamic theming styled-components, Emotion Full JavaScript power for theme logic
Performance critical Tailwind, CSS Modules, Vanilla Extract Zero/minimal runtime, static extraction
Type safety Vanilla Extract, Panda CSS, Stitches TypeScript-first with autocomplete
Server-side rendering CSS Modules, Tailwind, styled-components Mature SSR support, no hydration issues

CSS Integration with JavaScript Best Practices

  • Prefer classList over inline styles - Better performance, reusability, maintainability
  • Use getComputedStyle() for reading values, avoid layout thrashing with batch reads/writes
  • Choose CSS-in-JS for dynamic theming/component libraries, avoid for static sites
  • Use CSS Modules for scoped styles with zero runtime cost and explicit dependencies
  • Configure PostCSS with preset-env, autoprefixer, cssnano for modern CSS features
  • Leverage Shadow DOM for true encapsulation in Web Components
  • Expose customization with ::part() and CSS Custom Properties in Web Components
  • Follow framework conventions: Vue/Svelte scoped styles, React CSS Modules/Tailwind, Angular ViewEncapsulation
  • Consider zero-runtime solutions (Vanilla Extract, Panda CSS) for performance-critical apps
  • Use Constructable Stylesheets to share styles across multiple Shadow DOM instances