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>