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

  1. Unzip the download and open index.html in your browser. The board opens with three sample projects, so you can try everything straight away.
  2. Drag a card, open a task, switch to the list, calendar and timeline from the tabs at the top.
  3. Set your brand colour in one place (see Colours, themes, fonts).
  4. 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

PageWhat it does
BoardColumns of cards. Drag and drop with mouse, touch or keyboard, task limits per column, lanes by person or priority, quick add.
ListSortable table with inline editing, selection with bulk actions, grouping, column chooser, CSV export.
CalendarTasks on their due date. Drag to another day. On phones it becomes a small month with an agenda below.
TimelineOne bar per task from start to due date. Drag to move, drag an end to change the length, zoom by days or weeks.
OverviewOpen, due soon, overdue, finished this week, tasks by status, finished per day, workload, recent activity.
TeamPeople cards, add a person, change role, add or remove from the project.
New projectFour steps: details, workflow from a template, team, review.
SettingsProject details, columns, labels, appearance, export and import.
Sign inSign 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:

SettingMeaning
appNameThe product name used in page titles.
userName and email of the signed-in person when the app starts empty (seed: false).
seedtrue 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.
weekStartFirst day of the week in the calendar and timeline: 1 Monday, 0 Sunday.
localeLanguage 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.
dataStart with this data object instead of the browser copy. See Saving to a server.
onChangeA 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 ACCENTS in lanewise.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 }]
}
  • status is the id of a column. order is the position inside that column, starting at 0.
  • priority is urgent, high, medium, low or none.
  • start and due are 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.
  • key is the task number people see, for example WEB-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.typeOther fieldsWhen
addidA task was created (or restored with Undo).
updateidA task field, checklist or comment changed.
moveid, from, to, indexA task moved to another column or position.
removeidsOne or more tasks were deleted.
projectidA project was created, changed, deleted or opened.
columns, labels, peopleidA column, label or person changed.
syncThe 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

FunctionWhat 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

FunctionWhat 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

FunctionWhat it does
db.dataThe 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
});
EventWhen
lanewise:changeAfter every data change, and with type: 'filter' when a filter changes.
lanewise:prefsTheme, accent or density changed.
lanewise:modal-open, lanewise:modal-closeA 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: 25 near the top of lanewise.list.js.
  • The link list.html?mine=1 opens 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

KeyAction
NNew task
/Search tasks
1 2 3 4Board, list, calendar, timeline
?Show the shortcut list
Alt + arrowsMove the focused card, calendar task or timeline bar
MMove menu for the focused card
EscClose 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.