Lanewise documentation
A project management app interface in plain HTML, CSS and JavaScript: board, list, calendar and timeline on one shared data layer.
Quick start
- Unzip the download and open
index.htmlin your browser. The board opens with three sample projects, so you can try everything straight away. - Drag a card, open a task, switch to the list, calendar and timeline from the tabs at the top.
- Set your brand colour in one place (see Colours, themes, fonts).
- When you are ready for real data, read Saving to a server.
There is no build step and no framework. The kit is plain HTML, one CSS file and a few small JavaScript files with comments. It works from a folder on your computer and on any web host.
Lanewise is an interface kit. It does not include a server, a database or user accounts. Out of the box it keeps data in the browser of the person using it. You connect it to your own backend with one function.
Files
Lanewise/
index.html board
list.html task list (table)
calendar.html month calendar
timeline.html timeline with bars
overview.html project numbers and activity
team.html people
new-project.html four-step form for a new project
settings.html project, columns, labels, appearance, data
login.html sign in, create account, reset password
documentation.html this guide
assets/
css/lanewise.css all styles, colour tokens at the top
js/config.js YOUR SETTINGS (start here)
js/lanewise.core.js storage, theme, menus, dialogs, toasts
js/lanewise.store.js the data: projects, people, tasks
js/lanewise.ui.js sidebar, filters, shortcuts, small shared parts
js/lanewise.task.js task dialog and "New task"
js/lanewise.board.js board page
js/lanewise.list.js list page
js/lanewise.calendar.js calendar page
js/lanewise.timeline.js timeline page
js/lanewise.pages.js overview and team pages
js/lanewise.wizard.js new project form
js/lanewise.settings.js settings page
js/lanewise.auth.js sign-in form checks
js/lanewise.icons.js the icon set
js/sample-data.js sample projects for the demo (safe to delete)
Every app page loads the scripts in this order: config.js, lanewise.icons.js, lanewise.core.js, sample-data.js, lanewise.store.js, lanewise.ui.js, lanewise.task.js, then the script for that page. The sign-in page needs only the first three and lanewise.auth.js.
The pages
| Page | What it does |
|---|---|
| Board | Columns of cards. Drag and drop with mouse, touch or keyboard, task limits per column, lanes by person or priority, quick add. |
| List | Sortable table with inline editing, selection with bulk actions, grouping, column chooser, CSV export. |
| Calendar | Tasks on their due date. Drag to another day. On phones it becomes a small month with an agenda below. |
| Timeline | One bar per task from start to due date. Drag to move, drag an end to change the length, zoom by days or weeks. |
| Overview | Open, due soon, overdue, finished this week, tasks by status, finished per day, workload, recent activity. |
| Team | People cards, add a person, change role, add or remove from the project. |
| New project | Four steps: details, workflow from a template, team, review. |
| Settings | Project details, columns, labels, appearance, export and import. |
| Sign in | Sign in, create account and reset password forms with checks. |
All views read from the same data, and the filters at the top (search, assignee, label, priority) stay the same when you switch between board, list, calendar and timeline.
You do not need all pages. To remove one, delete its HTML file and its link in the sidebar or the view tabs of the other pages.
Settings (config.js)
Open assets/js/config.js. It is one object:
| Setting | Meaning |
|---|---|
appName | The product name used in page titles. |
user | Name and email of the signed-in person when the app starts empty (seed: false). |
seed | true fills the app with the sample projects on the first visit. Set to false for a real product; the app then starts with one empty project. |
weekStart | First day of the week in the calendar and timeline: 1 Monday, 0 Sunday. |
locale | Language for month and weekday names, for example 'en-GB' or 'de'. Texts on buttons stay in English; they are in the HTML and JavaScript files. |
data | Start with this data object instead of the browser copy. See Saving to a server. |
onChange | A function called after every change. Use it to save. |
The brand name and logo in the sidebar are plain HTML at the top of each page (class="side-brand"). Search and replace “Lanewise” across the HTML files to rename the product.
Colours, themes, fonts
All colours are CSS variables at the top of assets/css/lanewise.css. To rebrand, change three lines:
:root {
--brand: #0f766e; /* buttons, active states */
--brand-ink: #ffffff; /* text on top of the brand colour */
--brand-text: #0c625b; /* the brand colour as readable text on white */
}
The dark theme has its own three values under [data-theme="dark"]. Keep text contrast at 4.5 to 1 or more when you pick new colours.
Theme, accent and density
People choose these in Settings → Appearance. The choice is saved in their browser and applied before the page paints, so there is no flash.
- Theme: light, dark or the same as the device.
- Accent: teal, blue, violet, rose, ink. The list is
ACCENTSinlanewise.core.js; the colours are the[data-accent="…"]rules in the CSS. - Density: comfortable or compact (narrower columns, lower table rows).
To fix a theme for everyone, set it on the <html> tag (data-theme="dark") and remove the Appearance tab.
Colours for columns, labels, people and projects
Eight named colours are used for these: slate, blue, teal, green, amber, orange, rose, violet. They are the --c-… variables. Chips mix the colour with the page background, so they stay readable in both themes.
Fonts
The kit uses Figtree for text and Geist Mono for task numbers, loaded from Google Fonts in the <head> of each page. To change: replace the <link> tag and the --font and --mono variables. To use no web font at all, delete the link; the system font is the fallback.
Board column width
--col-w (292px) is the width of one column and --gap the space between columns.
How data is stored
Everything lives in one plain object. Out of the box it is saved in the browser (localStorage, key lanewise:data), so each person sees their own copy and nothing is sent anywhere. Two open tabs stay in sync.
{
version: 1,
me: 'u1', // id of the signed-in person
activeProject: 'p1',
people: [{ id, name, email, role, color }],
projects: [{ id, key, name, color, desc, counter, members: [personId],
columns: [{ id, name, color, limit, done }],
labels: [{ id, name, color }] }],
tasks: [{ id, key, projectId, title, desc, status, order, priority,
assignee, labels: [labelId], start, due,
checklist: [{ id, text, done }],
comments: [{ id, by, text, time }],
created, updated, doneAt }],
activity: [{ id, time, by, text, taskId }]
}
statusis the id of a column.orderis the position inside that column, starting at 0.priorityisurgent,high,medium,lowornone.startanddueare dates as text,YYYY-MM-DD, or empty.- One column per project has
done: true. Tasks in it count as finished and are never overdue. keyis the task number people see, for exampleWEB-12. It is made from the project key and a counter.
Settings → Data lets a person export this object as a JSON file, import one, or go back to the sample data.
Start empty
Set seed: false in config.js and remove the sample-data.js script tag from the pages. The app then starts with one empty project called “My project”.
Saving to a server
The simplest way to connect a backend is to load the whole object when the page opens and save it after every change.
1. Save
// assets/js/config.js
onChange: function (change, data) {
fetch('/api/workspace', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
}
2. Load
Either print the data into the page on the server, before config.js is read:
<script>window.LanewiseData = { ...data from your database... };</script>
<script src="assets/js/config.js"></script>
// and in config.js
data: window.LanewiseData || null,
or fetch it after the page has loaded:
fetch('/api/workspace')
.then(function (r) { return r.json(); })
.then(function (data) { Lanewise.db.load(data); });
Lanewise.db.load() replaces the data, redraws the page and does not call onChange.
Saving only what changed
Saving the whole object is fine for one person or a small team. When several people edit at the same time, send single changes to your own endpoints instead. The first argument of onChange tells you what happened:
change.type | Other fields | When |
|---|---|---|
add | id | A task was created (or restored with Undo). |
update | id | A task field, checklist or comment changed. |
move | id, from, to, index | A task moved to another column or position. |
remove | ids | One or more tasks were deleted. |
project | id | A project was created, changed, deleted or opened. |
columns, labels, people | id | A column, label or person changed. |
sync | The whole data was replaced (import or reset). |
onChange: function (change, data) {
if (change.type === 'move') {
fetch('/api/tasks/' + change.id + '/move', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ column: change.to, index: change.index })
});
}
}
The kit does not include a server, real-time updates between people, or conflict handling. Those belong to your backend. Check on the server who is allowed to change what; anything in the browser can be changed by the person using it.
Data functions
Every view uses the same small set of functions on Lanewise.db. You can call them from your own code or from the browser console. Each change saves, redraws the open view and calls onChange.
Tasks
| Function | What it does |
|---|---|
db.tasks(projectId, columnId) | Tasks in order. Both arguments are optional; without them you get the open project. |
db.task('WEB-12') | One task by number or id. |
db.add({ title, status, priority, assignee, labels, start, due, desc }) | Creates a task at the end of the column. Returns it. |
db.update(id, { … }) | Changes fields. A start date after the due date moves the due date with it. |
db.move(id, columnId, index) | Moves a task to a column and position. |
db.remove(id or [ids]) | Deletes. Returns a function that brings the tasks back (Undo). |
db.duplicate(id) | Copies a task, with an unticked checklist. |
db.comment(id, text) | Adds a comment from the signed-in person. |
db.isDone(task), db.isOverdue(task) | True or false. |
db.sortColumn(columnId, 'priority' or 'due') | Sorts the cards of one column. |
Projects, columns, labels, people
| Function | What it does |
|---|---|
db.projects(), db.project() | All projects; the open one. |
db.setProject(id) | Opens a project. |
db.addProject({ name, key, color, desc, members, columns, labels }) | Creates and opens a project. |
db.updateProject(id, { … }), db.removeProject(id) | Change or delete. The last project cannot be deleted. |
db.addColumn({ name, color, limit }), db.updateColumn(id, { … }), db.moveColumn(id, index), db.removeColumn(id, moveTasksTo) | Columns of the open project. |
db.addLabel({ name, color }), db.updateLabel(id, { … }), db.removeLabel(id) | Labels of the open project. |
db.people(), db.person(id), db.me() | Members of the open project; one person; the signed-in person. db.people(true) returns everyone. |
db.addPerson({ name, email, role }), db.updatePerson(id, { … }), db.removePerson(id) | People. Removing a person unassigns their tasks. |
Everything
| Function | What it does |
|---|---|
db.data | The whole object (read it; change it through the functions). |
db.load(object) | Replaces the data without calling onChange. |
db.exportJson(), db.importJson(text) | The data as text, and back. Import throws an error when the text is not a Lanewise export. |
db.reset() | Clears the browser copy and starts again (sample data, or empty with seed: false). |
db.setFilter(key, value), db.clearFilters() | Filters: q, assignee, label, priority. |
Date helpers are on Lanewise.date: today(), add('2026-03-01', 7), diff(a, b), short(), long(), valid().
Events
The kit sends events on document. Listen to them to add your own behaviour.
document.addEventListener('lanewise:change', function (e) {
console.log(e.detail.type, e.detail); // same object as in onChange
});
| Event | When |
|---|---|
lanewise:change | After every data change, and with type: 'filter' when a filter changes. |
lanewise:prefs | Theme, accent or density changed. |
lanewise:modal-open, lanewise:modal-close | A dialog opened or closed. e.detail.id is the dialog id. |
Board
- Drag and drop uses pointer events, so the same code handles mouse, pen and touch. On touch screens, press and hold a card for a moment, then drag; a quick swipe scrolls the board. The board scrolls by itself when you drag near an edge. Esc cancels a drag.
- Keyboard: focus a card, then Alt + arrow keys move it between columns or up and down. M opens a “Move to” menu. Each move is announced to screen readers.
- Task limits: set “most tasks” for a column from its menu. The count turns red when the column holds more. Nothing is blocked; it is a signal.
- Lanes: the Group button splits the board into rows by assignee or by priority. Dragging a card into another row changes its assignee or priority.
- Columns: rename, sort, collapse and delete from the column menu; add a column at the right end. Order, colour and the “done” column are in Settings → Columns.
- Filters hide cards without changing the order of the hidden ones.
The grouping and collapsed columns are remembered in the browser.
List, calendar, timeline
List
- Click a column heading to sort: first click ascending, second descending, third back to board order.
- Status, priority, assignee and due date can be changed in the row.
- Tick rows to move, assign, set priority or delete several tasks at once. Delete can be undone.
- Group by status, assignee or priority. Hide columns with the Columns button.
- Export CSV downloads the rows that match the current filters, as a UTF-8 file with a comma between values. A value that starts with
=,+,-or@gets a leading apostrophe, so a spreadsheet does not run it as a formula. - Without grouping, the list shows 25 tasks per page. Change
size: 25near the top oflanewise.list.js. - The link
list.html?mine=1opens the list filtered to the signed-in person.
Calendar
- A task sits on its due date. Drag it to another day; the start date moves with it, so the task keeps its length.
- Tasks without a due date are in the “No date” list. Drag one onto a day.
- A day shows three tasks; the rest are behind “+2 more”.
- Click a day number to create a task due that day.
- Keyboard: focus a task, Alt + ← → moves it one day, Alt + ↑ ↓ one week.
- Under 700px the calendar shows a count per day; tap a day to see its tasks below.
Timeline
- A bar runs from the start date to the due date. A task with only one date is one day long.
- Drag a bar to move it. Drag its left or right end to change one date.
- Keyboard: Alt + ← → moves a bar one day, Shift + Alt + ← → changes the due date.
- Tasks without dates show a “Set dates” button. Tasks outside the visible weeks show a button that jumps to them.
- Zoom: days (8 weeks) or weeks (16 weeks).
The timeline shows dates only. It has no dependencies between tasks, no critical path and no resource planning.
Task dialog
Clicking a task in any view opens the same dialog. Changes are saved as you make them; there is no Save button.
- Title, description, status, assignee, priority, labels, start and due date.
- Checklist with a progress bar. The card shows “3/5”.
- Comments from the signed-in person. Ctrl + Enter sends.
- Copy link: the address gets
#task=WEB-12, and opening that address opens the task again. - More menu: duplicate, move to the top of the column, delete (with Undo).
From your own code:
Lanewise.task.open('WEB-12');
Lanewise.task.create({ status: 'w-todo', due: '2026-11-02' }); // opens "New task"
Titles, descriptions and comments are shown as plain text. HTML typed by a person is never run.
Menus, dialogs, toasts
The small parts used across the kit are available for your own pages:
Lanewise.toast('Saved', 'check');
Lanewise.toast('Task deleted', 'trash', { label: 'Undo', run: function () { /* ... */ } });
Lanewise.modal.confirm({ title: 'Delete the project?', text: 'This cannot be undone.', ok: 'Delete', danger: true })
.then(function (yes) { if (yes) { /* ... */ } });
var m = Lanewise.modal.create({ title: 'Hello', body: '<p>Any HTML.</p>', foot: '<button class="btn" data-modal-close>Close</button>' });
Lanewise.modal.open(m);
Lanewise.menu(buttonElement, [
{ label: 'Rename', icon: 'edit', onSelect: function () {} },
'sep',
{ label: 'Delete', icon: 'trash', danger: true, onSelect: function () {} }
]);
Dialogs keep the keyboard focus inside, close with Esc and give the focus back to the button that opened them. Menus work with arrow keys. The body of a dialog is HTML: escape text that comes from people with Lanewise.escape().
Icons: Lanewise.icon('calendar') returns an SVG. The names are the keys in lanewise.icons.js.
Sign-in page
login.html has three forms: sign in, create account, reset password. The script checks the fields and shows messages next to them. In the demo, a correct form simply opens the board.
To connect your backend, remove data-demo from the form and set its action and method. The browser then sends the form to your server after the checks pass.
<form data-auth="signin" action="/session" method="post" novalidate>
The kit does not sign anyone in and does not protect pages. Check the session on your server before you send the app pages or any data.
Keyboard and accessibility
| Key | Action |
|---|---|
| N | New task |
| / | Search tasks |
| 1 2 3 4 | Board, list, calendar, timeline |
| ? | Show the shortcut list |
| Alt + arrows | Move the focused card, calendar task or timeline bar |
| M | Move menu for the focused card |
| Esc | Close a dialog or menu, cancel a drag |
- Everything that works with drag and drop also works with the keyboard.
- Moves, new tasks and deletions are announced through a live region.
- Text and controls keep a contrast of 4.5 to 1 or more in both themes and all accents.
- Focus is always visible. Animations are reduced when the device asks for less motion.
- Pages work from 320px wide. On phones the sidebar becomes a drawer and the task dialog a bottom sheet.
The kit was built with care for accessibility and tested with keyboard and automated checks. It has not been through a formal audit.
Browsers
Current versions of Chrome, Edge, Firefox and Safari. The CSS uses color-mix() and the separate translate and rotate properties, so the minimum is about Chrome 111, Edge 111, Firefox 113 and Safari 16.2. Internet Explorer is not supported.
The JavaScript is written without modern syntax that needs a build tool and has no dependencies.
Good to know
- Data is saved in the browser until you connect a server. Clearing site data removes it. The browser limit for this storage is around 5 MB, enough for some thousands of tasks.
- The views redraw from the data after each change. This is fast for the hundreds of tasks a project board usually holds; it is not made for tens of thousands of rows on one page.
- There are no file attachments, notifications, emails, time tracking, recurring tasks or permissions per person. The data layer is small and commented, so these can be added.
- “Invite” on the team page and in the new project form adds a person to the data. It does not send an email.
- Texts are in English. To translate, edit the HTML files and the strings in the JavaScript files.
- Sample names, emails and projects are invented.
Help and license
Questions or a bug? Reply to your purchase receipt email. Please include the browser and the steps that show the problem.
You may use Lanewise in unlimited projects for yourself and for clients, including paid products. You may not resell or share the kit itself. The full terms are in LICENSE.txt.
Fonts: Figtree and Geist Mono (SIL Open Font License), loaded from Google Fonts. Icons are drawn for this kit and included in the license.