Todo List
A to-do list with the feel of paper: hand-drawn ticks, notes, highlighters, swipe and drag, and a remembered look.
c2-todo-list is a task list that feels like a paper one. Tasks are plain text. Checking one draws an ink tick (or a
cross) and a pen stroke through the text, and a task you drop gets a red ✗. A progress ring beside the heading fills
as tasks are crossed off; set progress to bar, hero or none to draw it differently. The list’s icon is one of
twenty named list icons (or suggested from the heading: Groceries gets a cart), and any task can carry its own icon from
@c2n/task-icons, a highlighter for what matters, or a pen colour for its text.
Type a note after a dash in the add field (“Oat milk - 2 L barista”). Drag a
row up or down to reorder it. Swipe it left for Archive and Delete, or right for Done (both come from
c2-reorder-list); tap the row, swipe it back or press Escape to close it without doing anything.
On a desktop the same actions are in the row’s ⋯ menu (or a right-click) and on the keys E, Delete, X
(won’t do), F2 (edit) and I (icon). The menu also changes the task’s Highlight and Text colour from a
swatch submenu, and clicking a task’s icon opens the icon picker, so one task is styled in place without a panel.
Archived tasks collect at the bottom, and every archive or delete can be undone.
To change a task, choose Edit task in its menu (or press F2): its name and its note become two fields in place, and you can move between them freely. Enter in the name (or ⌘/Ctrl+Enter in the note), or anything that takes the focus away (a click outside the list, on another row or a menu), saves both and returns to the list. Escape cancels, an empty name keeps the old one, and an edit can be undone like the rest. Clicking a task never edits it: it shows the whole note, where the row shows only its first line.
Add customizable for a palette button. It opens a panel over the tasks, kept in the window, that styles the whole list: the list’s
icon (a dropdown with every task icon, the one suggested by the heading, or none), the background (the text colour follows
from it), a colour palette (a dropdown of Classic, Soft, Earth and Ocean, each with six
highlighters and five pens in a light and a dark variant), the done mark, the density
and the progress style. Changes apply at once; the × button, Escape or a press elsewhere closes the panel. With a storage-key those choices are
remembered in localStorage; persist-tasks remembers the tasks too.
Import
import '@c2n/components/todo-list'Usage
Press Space to pick up an item. Use Arrow keys, Home, or End to move it; Space drops and Escape cancels.
Dr. Martin, 14 rue Oberkampf
>Press Space to pick up an item. Use Arrow keys, Home, or End to move it; Space drops and Escape cancels.
6, on the vine
>500 g
>Press Space to pick up an item. Use Arrow keys, Home, or End to move it; Space drops and Escape cancels.
List icons
Set the list’s icon by what the list is for, with icon="groceries". Without one, the heading suggests it; icon="none"
leaves the ring empty, and any @c2n/task-icons name works too. The viewer picks from the
same set in the customize panel. The names are exported as todoListIcons:
work, personal, groceries, shopping, home, chores, health, fitness, travel, study, reading, ideas, projects, finance, family, events, meals, errands, schedule, goals
<c2-todo-list heading="Lisbon trip" icon="travel"></c2-todo-list>Heading and header actions
Add heading-editable and the heading becomes a c2-inline-edit: click it to rename the list,
Enter (or leaving the field) saves and Escape cancels. The list then fires heading-change with the trimmed
detail.heading, and the icon suggested from the heading follows the new name. An editable heading shows even while it
is empty, as heading-placeholder (“Untitled list” by default), and editHeading() opens it from script: a list that
was just created can ask for its name at once. readonly turns it back into plain text.
The actions slot puts your own controls in the header, such as a menu or a share button. They sit before the palette
button by default; actions-placement="end" puts them after it.
Press Space to pick up an item. Use Arrow keys, Home, or End to move it; Space drops and Escape cancels.
Early, before the queue
>list.addEventListener('heading-change', (event) => save({ title: event.detail.heading }))
await list.editHeading() // a new list asks for its nameTasks
tasks is an array of objects; only label is required. It can be set as a property or as a JSON attribute, and the
list updates it as the viewer works.
list.tasks = [
{ id: '1', label: 'Oat milk', note: '2 L barista', icon: 'milk' },
{ id: '2', label: 'Renew passport', highlight: 1, due: 'Today', urgent: true },
{ id: '3', label: 'Gym session', done: true },
]
list.addEventListener('tasks-change', (event) => save(event.detail.tasks))| Field | Description |
|---|---|
id | Stable identity. Generated when missing. |
label | The task, as plain text. |
note | More information, shown as a second line: a quantity, an address, a link. |
done | Checked off with a tick. |
dropped | Crossed off without being done (“won’t do”). Counts as closed in the progress. |
archived | In the archive section at the bottom: out of the list and the progress, restorable. |
icon | An optional @c2n/task-icons name. Tasks have no icon unless you give them one. |
highlight | A highlighter behind the task: its 1-based position in highlights (by default 1 yellow, 2 green, 3 blue, 4 pink, 5 orange, 6 violet). |
ink | The pen colour of the task’s text: its 1-based position in pens (by default 1 blue, 2 red, 3 green, 4 violet, 5 graphite). |
due | Short text after the task: Today, Fri, 9:30. |
urgent | Draws the due text in the red pen and counts the task in the summary. |
Tasks saved with the default colours’ names (highlight: 'yellow', ink: 'violet') are read as their position, and
the list writes positions back from then on.
Every change fires its own event (task-toggle, task-add, task-remove, task-archive, task-restore,
task-change, task-reorder) followed by tasks-change with the whole new list.
Your own colours
Because a task stores a position rather than a colour, the colours themselves are yours to supply. Set pens and
highlights to lists of { value, name } (value any CSS colour, var() included; name the friendly name on the
swatch, optional, falling back to Pen 2; a bare colour string works too) and the task menu offers those instead:
const pens = {
light: [
{ name: 'Ink', value: '#1f2937' },
{ name: 'Brand', value: '#4f46e5' },
],
dark: [
{ name: 'Ink', value: '#e5e7eb' },
{ name: 'Brand', value: '#a5b4fc' },
],
}
list.pens = pens.light
list.highlights = [{ name: 'Important', value: '#f59e0b' }]
// The tasks keep their positions, so a new list recolours every task in place.
themeToggle.addEventListener('change', () => (list.pens = isDark() ? pens.dark : pens.light))Here the tasks keep the same positions ("ink": 2, "highlight": 1) and only the lists differ:
Press Space to pick up an item. Use Arrow keys, Home, or End to move it; Space drops and Escape cancels.
Press Space to pick up an item. Use Arrow keys, Home, or End to move it; Space drops and Escape cancels.
Keep the same length and order in every theme. A highlighter is mixed into the list’s background, so give its full
colour rather than a pale tint. A position the current list does not have shows as no colour. As with every list you
give, the colours are used exactly as given: their contrast on your backgrounds is yours to check. defaultTodoPens and
defaultTodoHighlights are exported to start from.
The customize panel’s choices are lists too. backgrounds are { value, color, name }: value is the list’s
surface and color its text; the softer surfaces, lines and grey text are mixed from the two unless you give muted,
line and soft, and dark (worked out from a hex or rgb() value when missing) picks each palette’s dark variant.
The default background, which follows your CSS variables and the theme, is always offered first. palettes are
{ name, accent, pens, highlights, strength, dark }: the accent of the ticks, progress and Add button, and pen and
highlighter values by position, which recolour the matching position of pens and highlights while the palette is
chosen. dark overrides any of them on a dark background:
list.backgrounds = [
{ name: 'Cream', value: '#fffaeb', color: '#281e14' },
{ name: 'Midnight', value: '#101828', color: '#f0f0fa' },
]
list.palettes = [
{ name: 'Brand', accent: '#4f46e5', pens: ['#1f2937', '#4f46e5'], dark: { accent: '#a5b4fc', pens: ['#e5e7eb', '#a5b4fc'] } },
{ name: 'Forest', accent: '#15803d', highlights: ['#86efac'], strength: 30 },
]Open the palette button of this list to choose among its own backgrounds and palettes:
Press Space to pick up an item. Use Arrow keys, Home, or End to move it; Space drops and Escape cancels.
The default backgrounds and palettes were checked for contrast. A list you give replaces them and is used exactly as
given: choosing colours that read on each other, in every theme, is up to you. defaultTodoBackgrounds and defaultTodoPalettes are exported to start from.
Customization and storage
The viewer’s choices are a small object, { icon, background, palette, pen, doneMark, progress, density }, readable and
writable as the look property and announced by look-change. background and palette are positions in
backgrounds and palettes (0 is the default background); the default names ('night', 'earth') are still read. A palette is a colour scheme for the whole list: its
accent colours the ticks, the progress and the Add button, and its highlighters set the mood: Classic (bright,
blue), Soft (pale and airy, lavender), Earth (warm and muted, sienna) and Ocean (cool sand, seafoam and coral,
teal). It brings its own six highlighters and five pens for the tasks. The panel has no pen
choice, but look.pen set from script still replaces the palette’s accent with one of the pens. Each background sets its
text colour, and each palette’s accent, pens and highlighters were checked
against every background, plain and under every highlighter, in a light and a dark variant (a palette on the default
background follows a dark theme too), so any combination stays legible at 4.5:1 or better. The look is applied inside the component, so it wins over the CSS variables the page sets, and
Reset (or resetLook()) hands it back to the page and the theme. With storage-key="this-week" the look is
stored under c2-todo-list:this-week; give every list its own key.