Design System Docs
Home Architecture Getting Started Packages Playground
Home Architecture Getting Started Packages Playground
Components Styles Elements Feeds Tokens Icons Utilities Model & Builder

Advanced Component Usage

TypeScript, testing, and custom development patterns

Home Components

Advanced Usage

Getting Started

Where This Package Fits: Components sits at the top of the Application layer. It depends on all lower packages and provides ready-to-use web components. See full architecture.

TypeScript Integration

The components library provides comprehensive TypeScript support with full type definitions.

Type Imports

import {
  Components,
  ComponentRef,
  SlotConfiguration,
  ComponentEvents,
  CreateComponentFunction,
  TypedComponentRef
} from '@universityofmaryland/web-components-library';

// Component-specific types
import type {
  HeroProps,
  CardProps,
  NavigationConfig
} from '@universityofmaryland/web-components-library/types';

Typed Component References

// Get typed component reference
const hero = document.querySelector<HTMLElement & ComponentEvents>('umd-element-hero');

if (hero) {
  // TypeScript knows about component events
  hero.addEventListener('component:ready', (event) => {
    console.log('Hero component initialized', event.detail);
  });

  // Set attributes with type safety
  hero.setAttribute('data-theme', 'dark'); // Valid
  // hero.setAttribute('data-theme', 'invalid'); // TypeScript error
}

Creating Components Programmatically

import { Components } from '@universityofmaryland/web-components-library';

// Initialize components
Components.hero.base();

// Create element programmatically with type safety
const createTypedHero = (): HTMLElement => {
  const hero = document.createElement('umd-element-hero') as HTMLElement & {
    setTheme: (theme: 'light' | 'dark') => void;
    updateContent: (content: HeroContent) => void;
  };

  hero.setAttribute('data-theme', 'dark');
  hero.setAttribute('data-display', 'overlay');

  return hero;
};
Key Types:
  • ComponentRef - Base reference returned by component factories
  • SlotConfiguration - Defines allowed slots and their constraints
  • ComponentEvents - Interface for component event handling
  • CreateComponentFunction - Standard signature for component creation

Event Handling

Components emit custom events for lifecycle and interaction handling.

Component Lifecycle Events

const card = document.querySelector('umd-element-card');

// Component initialization
card.addEventListener('component:ready', (event) => {
  console.log('Component initialized with:', event.detail);
});

// Content changes
card.addEventListener('component:updated', (event) => {
  console.log('Component updated:', event.detail.changes);
});

// Before removal
card.addEventListener('component:destroy', (event) => {
  console.log('Component being removed');
});

Reactive Components

Components support reactive updates through observed attributes.

Observed Attributes

// Visual state changes
const drawer = document.querySelector('umd-element-navigation-drawer');

// Open drawer programmatically
drawer.setAttribute('is-visual-open', 'true');

// Close drawer
drawer.setAttribute('is-visual-open', 'false');

// Listen for state changes
const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    if (mutation.attributeName === 'is-visual-open') {
      const isOpen = drawer.getAttribute('is-visual-open') === 'true';
      console.log('Drawer state:', isOpen ? 'open' : 'closed');
    }
  });
});

observer.observe(drawer, { attributes: true });

Responsive Behavior

// Trigger component resize calculations
const hero = document.querySelector('umd-element-hero');

// Manual resize trigger
hero.setAttribute('resize', 'true');

// Automatic resize handling
const resizeObserver = new ResizeObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.target.tagName.startsWith('UMD-ELEMENT-')) {
      entry.target.setAttribute('resize', 'true');
    }
  });
});

// Observe all components
document.querySelectorAll('[class*="umd-element-"]').forEach((element) => {
  resizeObserver.observe(element);
});

Testing Components

Best practices for testing components in your application.

Jest Testing Setup

// __tests__/components.test.js
import { Components } from '@universityofmaryland/web-components-library';

describe('UMD Components', () => {
  beforeEach(() => {
    document.body.innerHTML = '';
    Components.card.standard();
  });

  test('creates card component', () => {
    const card = document.createElement('umd-element-card');
    card.innerHTML = `
      <h3 slot="headline">Test Headline</h3>
      <p slot="text">Test content</p>
    `;

    document.body.appendChild(card);

    expect(card.shadowRoot).toBeTruthy();
    expect(card.querySelector('[slot="headline"]').textContent)
      .toBe('Test Headline');
  });

  test('handles missing required slots', () => {
    const card = document.createElement('umd-element-card');
    // Missing headline slot
    card.innerHTML = '<p slot="text">Test content</p>';

    // Component should still render
    document.body.appendChild(card);
    expect(card.shadowRoot).toBeTruthy();
  });
});

Framework Integration

Web components work natively in any framework. Here are common integration patterns.

React

import { useEffect, useRef } from 'react';

// Initialize components once at app root
import { LoadStructuralComponents } from '@universityofmaryland/web-components-library/structural';
LoadStructuralComponents();

function UmdHero({ title, image, children }) {
  return (
    <umd-element-hero data-display="overlay">
      <img slot="image" src={image} alt="" />
      <h1 slot="headline">{title}</h1>
      <div slot="text">{children}</div>
    </umd-element-hero>
  );
}

Vue

// main.js — tell Vue to ignore umd-* tags
app.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('umd-');

// In vite.config.js
export default defineConfig({
  plugins: [
    vue({
      template: {
        compilerOptions: {
          isCustomElement: (tag) => tag.startsWith('umd-'),
        },
      },
    }),
  ],
});

Angular

// app.module.ts
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';

@NgModule({
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}

Building Custom Components

Extend the component library with your own custom components using the Model package.

Custom Component Template

import {
  Attributes,
  Model,
  Register,
  Slots,
  Lifecycle
} from '@universityofmaryland/web-model-library';

// Define your component
const tagName = 'umd-custom-feature';

const slots = {
  headline: {
    allowedElements: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
    required: true
  },
  text: {
    allowedElements: ['p', 'div'],
    required: false
  },
  media: {
    allowedElements: ['img', 'video', 'iframe'],
    required: false
  }
};

const createComponent = (element) => {
  const headline = Slots.headline.default({ element });
  const text = Slots.text.default({ element });
  const media = element.querySelector('[slot="media"]');

  // Create shadow DOM structure
  const template = `
    <style>
      :host {
        display: block;
        padding: 2rem;
      }
      .feature-content {
        max-width: 800px;
        margin: 0 auto;
      }
    </style>
    <div class="feature-content">
      ${headline ? `<h2>${headline.outerHTML}</h2>` : ''}
      ${media ? `<div class="media">${media.outerHTML}</div>` : ''}
      ${text ? `<div class="text">${text.outerHTML}</div>` : ''}
    </div>
  `;

  return {
    element: element,
    template: template
  };
};

// Register the component
export const CustomFeature = Register.webComponent({
  tagName,
  slots,
  createComponent
});
Full Custom Component Guide: For a comprehensive guide to building custom components with the Model and Builder packages, see the Model & Builder documentation.

Performance Optimization

Techniques for optimizing component performance in production.

Lazy Loading Components

// Load components only when needed
const loadHeroComponents = () =>
  import('@universityofmaryland/web-components-library').then(({ Components }) => {
    Components.hero.base();
    Components.hero.expand();
  });

// Intersection Observer for lazy loading
const heroObserver = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      loadHeroComponents().then(() => {
        console.log('Hero components loaded');
      });
      heroObserver.disconnect();
    }
  });
});

// Observe hero section
const heroSection = document.querySelector('.hero-section');
if (heroSection) {
  heroObserver.observe(heroSection);
}

Bundle Optimization

// webpack.config.js
module.exports = {
  optimization: {
    splitChunks: {
      cacheGroups: {
        umdComponents: {
          test: /[\\/]node_modules[\\/]@universityofmaryland[\\/]/,
          name: 'umd-components',
          priority: 10,
          chunks: 'all'
        }
      }
    }
  }
};

// Vite configuration
export default {
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          'umd-components': ['@universityofmaryland/web-components-library'],
          'umd-styles': ['@universityofmaryland/web-styles-library']
        }
      }
    }
  }
};
Performance Tips
  • Load only the components you use
  • Use dynamic imports for below-the-fold components
  • Enable production mode for smaller bundles
  • Consider server-side rendering for SEO-critical content
  • Use the loading="lazy" attribute on images within components

Related Resources

Components API Reference

Complete API documentation for all component exports.

View API

Component Usage Guide

Slot-based markup patterns for every component.

View Guide

Model & Builder

Underlying model and builder patterns for custom components.

View Docs

Developer Tools

Learning

UMD Links