
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
dockit-elements
Advanced tools
Dockit Element is a lightweight, zero-dependency UI library for building reactive web interfaces in TypeScript or JavaScript.
Dockit Element is a lightweight, zero-dependency UI library for building reactive web interfaces in TypeScript or JavaScript.
Perfect for:
Philosophy: Start simple, build up gradually. No magic, no hidden complexity—just clear, explicit code that you control.
Getting Started
Building with Elements
Working with Components
Advanced Features
Reference
Dockit Element helps you build web interfaces using elements and components.
Think of it like building with LEGO blocks—start with simple pieces, then combine them into more complex structures.
Using npm (recommended):
npm install dockit-element
Manual installation:
Copy src/index.ts into your project and import from there.
Let's create the simplest possible Dockit app—a single text element:
import { div } from 'dockit-element';
// Create a div element with text inside
const myElement = div(["Hello, World!"]);
That's it! You've created your first Dockit element. The div() function creates a virtual element that represents an HTML <div>.
Other elements: Dockit provides functions for all standard HTML elements:
import { h1, p, button, span } from 'dockit-element';
const heading = h1(["Welcome!"]);
const paragraph = p(["This is a paragraph."]);
const btn = button(["Click me!"]);
To show your elements in the browser, you need two things:
1. An HTML file with a container:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Dockit App</title>
</head>
<body>
<div id="app"></div>
<script src="dist/bundle.js"></script>
</body>
</html>
2. Mount your element to the container:
import { div, DockitElementRoot } from 'dockit-element';
// Create an element
const myElement = div(["Hello, Dockit!"]);
// Find the container in your HTML
const container = document.getElementById('app')!;
// Create a root and render
const root = new DockitElementRoot(container, myElement);
root.render();
What's happening here?
DockitElementRoot is your app's entry point—it connects your Dockit elements to the actual browser DOMcontainer is where your app will appear on the pagerender() displays your element in the browserUpdating your app:
root.replace(newElement)root.update()A typical Dockit project structure:
my-app/
src/
index.ts # Your app entry point
components/ # Your Dockit components (optional)
public/
index.html # Your HTML file
package.json
tsconfig.json
Minimal tsconfig.json:
{
"compilerOptions": {
"target": "es6",
"module": "esnext",
"strict": true,
"outDir": "./dist"
},
"include": ["src/**/*"]
}
Build with:
npm run build # Compile TypeScript
You'll need a bundler (like esbuild, webpack, or vite) to create the bundle.js file referenced in your HTML.
Elements can be nested and combined to create complex UIs:
import { div, h1, p, button } from 'dockit-element';
const myCard = div([
h1(["Welcome to Dockit"]),
p(["Build amazing UIs with ease."]),
button(["Get Started"])
]);
Key concepts:
Adding attributes:
import { div, input } from 'dockit-element';
const myInput = input([], {
id: "username",
placeholder: "Enter your name",
type: "text"
});
const container = div([myInput], {
id: "input-container",
className: "form-group"
});
The second argument to element functions is an options object where you can specify:
id: Element IDclassName: CSS classesstyle: Inline or scoped styles (more on this below)events: Event handlersDockit supports powerful scoped styling that's automatically managed for you.
Basic styling:
import { div } from 'dockit-element';
const styledDiv = div(["Styled content"], {
style: {
default: {
color: 'blue',
padding: '20px',
backgroundColor: '#f0f0f0',
borderRadius: '8px'
}
}
});
Hover effects (pseudo-selectors):
const hoverDiv = div(["Hover over me!"], {
style: {
default: {
padding: '1rem',
backgroundColor: '#4e54c8',
color: 'white',
cursor: 'pointer'
},
pseudo: {
':hover': {
backgroundColor: '#8f94fb',
transform: 'scale(1.05)'
}
}
}
});
Responsive styles (media queries):
const responsiveDiv = div(["I adapt to screen size"], {
style: {
default: {
fontSize: '24px',
padding: '2rem'
},
media: {
'(max-width: 600px)': {
fontSize: '16px',
padding: '1rem'
}
}
}
});
Why this is powerful:
Add interactivity with event handlers:
import { button } from 'dockit-element';
const clickableButton = button(["Click me!"], {
events: {
click: () => {
alert("Button clicked!");
}
}
});
Multiple events:
const input = input([], {
events: {
input: (e) => console.log("Value:", e.target.value),
focus: () => console.log("Input focused"),
blur: () => console.log("Input blurred")
}
});
Common events: click, input, change, submit, focus, blur, keydown, keyup, mouseenter, mouseleave
Components are reusable pieces of UI. The simplest form is a function that returns elements:
import { div, h1, p } from 'dockit-element';
function WelcomeCard({ name, message }: { name: string; message: string }) {
return div([
h1([`Welcome, ${name}!`]),
p([message])
], {
style: {
default: {
border: '1px solid #ccc',
padding: '1rem',
borderRadius: '8px'
}
}
});
}
// Use it:
const card = WelcomeCard({
name: "Alice",
message: "Thanks for using Dockit!"
});
const root = new DockitElementRoot(
document.getElementById('app')!,
card
);
root.render();
When to use function components:
For components that need to manage state and update themselves, use class-based components:
import { Component, div, h1, button, span, DockitElementRoot } from 'dockit-element';
class Counter extends Component<{ count: number }> {
constructor(initialCount = 0) {
super({ count: initialCount });
this.updateView(); // Important: initialize the view
}
renderView() {
this.children = [
h1(["Counter App"]),
div([
span([`Current count: ${this.state.count}`])
]),
button(["Increment"], {
events: {
click: () => {
// setState automatically updates the UI
this.setState({ count: this.state.count + 1 });
}
}
}),
button(["Reset"], {
events: {
click: () => this.setState({ count: 0 })
}
})
];
}
}
// Use it:
const root = new DockitElementRoot(
document.getElementById('app')!,
new Counter(0)
);
root.render();
Key points:
Component<StateType> where StateType is your state object shapesuper(initialState)this.updateView() at the end of the constructorrenderView() methodthis.setState() - this triggers automatic re-renderingWhen to use class components:
Build complex UIs by combining simple components:
import { Component, div, h1, p, button } from 'dockit-element';
// Simple function component
function Card({ title, content }: { title: string; content: string }) {
return div([
h1([title]),
p([content])
], {
style: {
default: {
border: '1px solid #ddd',
padding: '1rem',
margin: '0.5rem',
borderRadius: '4px'
}
}
});
}
// Stateful component using simple components
class CardList extends Component<{ cards: Array<{ title: string; content: string }> }> {
constructor() {
super({
cards: [
{ title: "Card 1", content: "First card content" },
{ title: "Card 2", content: "Second card content" }
]
});
this.updateView();
}
addCard = () => {
const newCard = {
title: `Card ${this.state.cards.length + 1}`,
content: `Card ${this.state.cards.length + 1} content`
};
this.setState({
cards: [...this.state.cards, newCard]
});
};
renderView() {
this.children = [
button(["Add Card"], {
events: { click: this.addCard }
}),
div(
this.state.cards.map(card => Card(card))
)
];
}
}
Composition patterns:
Dockit uses a virtual DOM to efficiently update your UI. Here's what happens:
1. You create elements:
const myElement = div(["Hello"]);
This creates a virtual representation of the UI in memory.
2. Dockit renders to the real DOM:
root.render();
Dockit converts the virtual elements into actual DOM elements.
3. When state changes:
this.setState({ count: this.state.count + 1 });
Dockit:
renderView() to create a new virtual treeWhy this matters:
Performance tip: For large lists, use the key prop to help Dockit track items:
div(
items.map(item =>
div([item.name], { key: item.id })
)
)
Add visual polish with CSS animations and transitions:
Simple transitions:
const fadeIn = div(["I fade in!"], {
style: {
default: {
animation: 'fadeIn 1s ease-in',
padding: '1rem',
backgroundColor: '#4e54c8',
color: 'white'
},
animation: {
keyframes: {
fadeIn: {
'0%': { opacity: '0', transform: 'translateY(-20px)' },
'100%': { opacity: '1', transform: 'translateY(0)' }
}
},
options: {
name: 'fadeIn',
duration: 1000,
easing: 'ease-in'
}
}
}
});
Hover transitions:
const hoverButton = button(["Hover me"], {
style: {
default: {
padding: '10px 20px',
backgroundColor: '#4e54c8',
color: 'white',
border: 'none',
borderRadius: '4px',
transition: 'all 0.3s ease',
cursor: 'pointer'
},
pseudo: {
':hover': {
backgroundColor: '#8f94fb',
transform: 'translateY(-2px)',
boxShadow: '0 4px 8px rgba(0,0,0,0.2)'
}
}
}
});
State-based animations:
class FadeToggle extends Component<{ visible: boolean }> {
constructor() {
super({ visible: true });
this.updateView();
}
toggle = () => {
this.setState({ visible: !this.state.visible });
};
renderView() {
const fadeStyle = {
default: {
animation: this.state.visible ? 'fadeIn 0.5s' : 'fadeOut 0.5s',
animationFillMode: 'forwards'
},
animation: {
keyframes: {
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' }
},
fadeOut: {
'0%': { opacity: '1' },
'100%': { opacity: '0' }
}
}
}
};
this.children = [
button([this.state.visible ? "Hide" : "Show"], {
events: { click: this.toggle }
}),
this.state.visible ? div(["Hello!"], { style: fadeStyle }) : null
];
}
}
Add custom methods to encapsulate complex logic:
class Timer extends Component<{ seconds: number; running: boolean }> {
private interval?: number;
constructor() {
super({ seconds: 0, running: false });
this.updateView();
}
start = () => {
if (!this.state.running) {
this.setState({ running: true });
this.interval = window.setInterval(() => {
this.setState({ seconds: this.state.seconds + 1 });
}, 1000);
}
};
stop = () => {
if (this.state.running) {
clearInterval(this.interval);
this.setState({ running: false });
}
};
reset = () => {
this.stop();
this.setState({ seconds: 0 });
};
// Cleanup when component is removed
destroy = () => {
this.stop();
};
renderView() {
this.children = [
h1([`Time: ${this.state.seconds}s`]),
button([this.state.running ? "Stop" : "Start"], {
events: {
click: this.state.running ? this.stop : this.start
}
}),
button(["Reset"], {
events: { click: this.reset }
})
];
}
}
Pass dynamic content to components:
function Container({ title, children }: { title: string; children: any[] }) {
return div([
h1([title]),
div(children, {
style: {
default: { padding: '1rem' }
}
})
], {
style: {
default: {
border: '2px solid #ccc',
borderRadius: '8px',
margin: '1rem'
}
}
});
}
// Use it:
const myContainer = Container({
title: "My Section",
children: [
p(["This is some content."]),
button(["Click me!"])
]
});
Do:
this.setState() to update statethisdestroy methodDon't:
this.state directlythis.updateView() in the constructorTest Dockit components using any DOM testing library (e.g., Jest with jsdom):
import { Counter } from './Counter';
describe('Counter', () => {
test('starts at initial value', () => {
const counter = new Counter(5);
const el = counter.render();
expect(el.textContent).toContain('5');
});
test('increments when button clicked', () => {
const counter = new Counter(0);
const el = counter.render();
const button = el.querySelector('button')!;
button.dispatchEvent(new MouseEvent('click', { bubbles: true }));
expect(el.textContent).toContain('1');
});
test('resets to zero', () => {
const counter = new Counter(10);
const el = counter.render();
const resetButton = el.querySelectorAll('button')[1];
resetButton.dispatchEvent(new MouseEvent('click', { bubbles: true }));
expect(el.textContent).toContain('0');
});
});
Testing tips:
.dispatchEvent() to simulate user interactionsTo use Dockit components as a library in other projects:
1. Clean up your entry point:
Remove any demo code from src/index.ts:
// Remove or comment out:
// const container = document.getElementById("app")!;
// const root = new DockitElementRoot(container, MyApp());
// root.render();
2. Export your components:
// src/index.ts
export { Component, Element, DockitElementRoot };
export { div, span, h1, h2, p, button, input /* ... all elements */ };
export { MyCustomComponent } from './components/MyCustomComponent';
3. Configure package.json:
{
"name": "my-dockit-library",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": ["dist"],
"scripts": {
"build": "tsc",
"prepublishOnly": "npm run build"
}
}
4. Build and publish:
npm run build
npm publish
Using your library:
import { Component, div, button } from 'my-dockit-library';
import { MyCustomComponent } from 'my-dockit-library';
Problem: State changes but UI doesn't update.
Solutions:
this.setState() instead of directly modifying this.statethis.updateView() at the end of the constructorDockitElementRoot to mount your appProblem: My styles don't appear in the browser.
Solutions:
style prop on elements, not external CSS filesDockitElementRoot has rendered (styles are injected during render)<style> tagdefault property in the style objectProblem: Click events or other events don't fire.
Solutions:
{ click: () => this.handleClick() } not { click: this.handleClick }click, not onClick)events propProblem: TypeScript complains about types.
Solutions:
document.getElementById('app')! (with !) to assert the element existsclass MyComponent extends Component<{ count: number }>tsconfig.json has "strict": trueclass LoginForm extends Component<{ username: string; password: string }> {
constructor() {
super({ username: '', password: '' });
this.updateView();
}
handleSubmit = (e: Event) => {
e.preventDefault();
console.log('Login:', this.state);
};
renderView() {
this.children = [
form([
input([], {
type: 'text',
placeholder: 'Username',
events: {
input: (e) => this.setState({ username: (e.target as HTMLInputElement).value })
}
}),
input([], {
type: 'password',
placeholder: 'Password',
events: {
input: (e) => this.setState({ password: (e.target as HTMLInputElement).value })
}
}),
button(['Login'], { type: 'submit' })
], {
events: { submit: this.handleSubmit }
})
];
}
}
Yes! Dockit is standalone and can coexist with other libraries. You can:
Just make sure each DockitElementRoot has its own container element.
console.log in your renderView() and event handlers<style> tag to see generated CSSWe welcome contributions!
How to contribute:
git checkout -b my-featureWhat to contribute:
Guidelines:
Questions or ideas?
MIT License
Copyright (c) 2025 Dockit Elements
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
FAQs
Dockit Element is a lightweight, zero-dependency UI library for building reactive web interfaces in TypeScript or JavaScript.
We found that dockit-elements demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.