Framework Integration

pudge-ui components are defined as CSS + HTML specs. This guide shows how to wrap them as components in popular frameworks. All examples use the Push Button (.push-btn) as the canonical example.

Vanilla HTML/CSS

The baseline — what the specs provide directly. Include the CSS and use the HTML structure from the spec.

<!-- Include pudge-ui tokens + component CSS -->
<link rel="stylesheet" href="pudge-ui.css" />

<!-- Use the component -->
<button class="push-btn">MENU</button>
<button class="push-btn active">AF-ON</button>
<button class="push-btn lg">PLAY</button>
<button class="push-btn rubber">START</button>

For interactive components (toggles, steppers, etc.), wire event listeners using data attributes:

<div data-toggle>
  <div class="toggle-track">
    <div class="toggle-thumb"></div>
  </div>
</div>

<script>
  document.querySelectorAll('[data-toggle]').forEach(el => {
    el.addEventListener('click', () => {
      el.querySelector('.toggle-track')?.classList.toggle('on');
    });
  });
</script>

React

Wrap the CSS class and HTML structure in a React component. Manage state with hooks.

// PushButton.tsx
import './pudge-ui.css';

interface PushButtonProps {
  children: React.ReactNode;
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
  material?: 'panel' | 'rubber' | 'glossy';
  active?: boolean;
  disabled?: boolean;
  onClick?: () => void;
}

export function PushButton({
  children, size, material, active, disabled, onClick,
}: PushButtonProps) {
  const classes = [
    'push-btn',
    size && size !== 'md' ? size : '',
    material && material !== 'panel' ? material : '',
    active ? 'active' : '',
  ].filter(Boolean).join(' ');

  return (
    <button className={classes} disabled={disabled} onClick={onClick}>
      {children}
    </button>
  );
}

For toggle components, manage state with useState:

// ToggleSwitch.tsx
// import { useState } from 'react';
// import './pudge-ui.css';

export function ToggleSwitch({ defaultOn = false, onChange }) {
  const [on, setOn] = useState(defaultOn);

  return (
    <div data-toggle onClick={() => { setOn(!on); onChange?.(!on); }}>
      <div className={"toggle-track " + (on ? "on" : "")}>
        <div className="toggle-thumb" />
      </div>
    </div>
  );
}

Vue

Same pattern as a Vue Single File Component:

<!-- PushButton.vue -->
<template>
  <button :class="classes" :disabled="disabled" @click="$emit('click')">
    <slot />
  </button>
</template>

<!-- script setup -->
<!-- import { computed } from 'vue'; -->
<!--
const props = defineProps({
  size: { type: String, default: 'md' },
  material: { type: String, default: 'panel' },
  active: Boolean,
  disabled: Boolean,
});

const classes = computed(() => [
  'push-btn',
  props.size !== 'md' && props.size,
  props.material !== 'panel' && props.material,
  props.active && 'active',
].filter(Boolean));
-->

Svelte

<!-- PushButton.svelte -->
<!-- export let size = 'md'; -->
<!-- export let material = 'panel'; -->
<!-- export let active = false; -->
<!-- export let disabled = false; -->
<!--
$: classes = [
  'push-btn',
  size !== 'md' && size,
  material !== 'panel' && material,
  active && 'active',
].filter(Boolean).join(' ');
-->

<button class={classes} {disabled} on:click>
  <slot />
</button>

Web Components

Wrap as a Custom Element with Shadow DOM for full encapsulation:

class PudgePushButton extends HTMLElement {
  static observedAttributes = ['size', 'material', 'active', 'disabled'];

  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() { this.render(); }
  attributeChangedCallback() { this.render(); }

  render() {
    const size = this.getAttribute('size') || 'md';
    const material = this.getAttribute('material') || 'panel';
    const active = this.hasAttribute('active');
    const disabled = this.hasAttribute('disabled');
    const classes = ['push-btn',
      size !== 'md' && size,
      material !== 'panel' && material,
      active && 'active',
    ].filter(Boolean).join(' ');

    this.shadowRoot.innerHTML =
      '<style>@import "./pudge-ui.css";</style>' +
      '<button class="' + classes + '">' +
      '<slot></slot></button>';
  }
}
customElements.define('pudge-push-btn', PudgePushButton);

Then use in HTML:

<pudge-push-btn size="lg" material="rubber">START</pudge-push-btn>

Same spec, any framework. The CSS translates cleanly to any rendering engine.

Pudge