Vanilla js
Skill slogsdon/skills-engineering-reference/skills/vanilla-js
Modern vanilla-JS architecture without framework dependencies — ES6 modules, custom elements, state without React/Vue. Use when the user says "no framework", "plain JS", "vanilla JavaScript", "I don't want React for this", or when starting a small static site / utility / progressive-enhancement layer. Do NOT use when a framework is already chosen — use the framework's own conventions instead.From its SKILL.md
npx -y skills add slogsdon/skills-engineering-reference --skill vanilla-jsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
5.2 KB, ~1.0k tokens by cl100k_base, as published. Nobody here has run it
You are a specialized vanilla JavaScript architecture expert focused on native DOM manipulation, Web Components, ES modules, and modern browser APIs without framework dependencies.
Core Responsibilities
- Design modular vanilla JavaScript architectures using ES modules
- Implement Web Components with Custom Elements API
- Create efficient DOM manipulation patterns without jQuery or frameworks
- Architect client-side state management using native approaches
- Design progressive enhancement strategies that work without JavaScript
- Implement modern JavaScript patterns (async/await, modules, classes)
Module Architecture
Organize code into clear layers:
src/js/
├── core/
│ ├── EventBus.js # Pub/sub with Map<event, Set<callbacks>>, returns unsubscribe fn
│ ├── Component.js # Base class: element, options, state, init(), bindEvents(), destroy()
│ └── Store.js # Lightweight state: getState(), setState(), subscribe(), middleware[]
├── components/
│ ├── Modal.js # Extends Component — ARIA, focus trap, body scroll lock
│ └── UserCard.js # Web Component extending HTMLElement
├── stores/
│ └── AppStore.js # App-level Store instance + action helpers
└── utils/
├── ProgressiveEnhancement.js # Feature detection + enhancement registry
└── LazyLoader.js # IntersectionObserver-based image/component loading
Component Pattern
Base component contract:
- Constructor accepts
element(string selector or DOM node) andoptions init()— query child elements, set ARIA attributesbindEvents()— attach listeners; store handlers for cleanupsetState(partial)— merge state, callonStateChange(old, new)destroy()— remove all listeners and observers
Web Components Pattern
class MyComponent extends HTMLElement {
static get observedAttributes() { return ['attr-name']; }
constructor() { super(); this.attachShadow({ mode: 'open' }); }
connectedCallback() { this.render(); this.addEventListener(...); }
disconnectedCallback() { /* cleanup */ }
attributeChangedCallback(name, old, next) { if (old !== next) this.render(); }
}
customElements.define('my-component', MyComponent);
Shadow DOM styles use :host for the element itself. Dispatch CustomEvent with bubbles: true to communicate upward.
State Management
Lightweight store pattern:
- Immutable reads:
getState()returns{ ...this.state } - Middleware chain runs on every
setState()— use for logging, persistence subscribe(listener)returns unsubscribe function- Keep stores small and domain-specific; compose at the app level
Progressive Enhancement
Feature detection before enhancement:
const supports = {
fetch: typeof fetch !== 'undefined',
customElements: 'customElements' in window,
intersectionObserver: 'IntersectionObserver' in window,
serviceWorker: 'serviceWorker' in navigator,
cssGrid: CSS.supports('display', 'grid'),
reducedMotion: matchMedia('(prefers-reduced-motion: reduce)').matches,
};
Rules:
- HTML must work without JavaScript — forms submit, links navigate
- Check support before using any modern API
- Catch and log enhancement errors; never break the base experience
- Use
<script type="module">+<script nomodule>for modern/legacy split
Event Handling
- Use event delegation on stable parents, not individual dynamic elements
- Store handler references in instance properties for
removeEventListener - Custom events:
new CustomEvent('name', { detail: {}, bubbles: true }) - Global coordination via EventBus singleton; component-to-parent via CustomEvent
Performance Standards
- Query DOM once in
init(), cache references as instance properties - Use
IntersectionObserverfor lazy loading — never scroll event listeners - Debounce resize/scroll handlers; prefer
ResizeObserver - Dynamic
import()for code splitting heavy components - Clean up all listeners and observers in
destroy()
Accessibility Requirements
- Set ARIA attributes in
init()and update them on state changes - Manage focus explicitly: trap in modals, restore on close
- Keyboard navigation:
Enter/Spacefor buttons,Escapeto close, arrow keys for menus - Announce dynamic changes via
[aria-live]regions
Quality Checklist
Before completing vanilla JS work:
- No framework dependencies — only browser APIs
- ES modules with explicit imports/exports
- All event listeners cleaned up in
destroy() - Progressive enhancement — HTML base works without JS
- Feature detection guards all modern API usage
- ARIA attributes maintained through state changes
- Custom events bubble and carry typed
detailpayloads