HTML elements for sheets people review: layout, charts, media, live controls, reader replies and 3D. One file, two kit links, no build step and no network.
Version 1.8.0 · prototypes to click through and boards to rearrange on a canvas, rows that scroll sideways, questions answered beside the content and sent as one reply, reply forms that keep unsent answers through a reload, a full-screen image viewer and readable times, a page that widens with the screen, riso by default, riso-night, eight flair pieces, a bare kit and styles · manifest.json lists every element, attribute, chart type and token
Start a sheet
Link the pinned kit: one stylesheet, one deferred script.
Pick a theme on body and write tk-* elements; data goes in application/json scripts.
Run tacksheet.py sheet validate, then tack the file with interactive: true.
The kit exposes Tacksheet.state, Tacksheet.on(), Tacksheet.chart() and Tacksheet.define() for your own code, and every colour, face and radius is a CSS custom property you can override.
Riso is the default: a body without data-tk-theme prints in two inks on riso stock. riso-night is the same printing on deep navy stock, and auto moves between the two with the reader’s system. In both riso themes the page column sits on a translucent reading panel, so type stays crisp over the texture (--tk-panel:transparent turns it off). The other looks stay for sheets that need them. Each has its own type, rules and colour-blind-safe chart palette. Pick one to restyle this whole page, charts and 3D included. Override any token (--tk-accent, --tk-font-head, --tk-radius, --tk-texture…) to make a sheet your own. Bare (none) keeps structure and behaviour only: it is the look of the bare kit below.
The bare kit
For a one-off look, link tacksheet-base.css instead of tacksheet.css. It keeps every behaviour and all the structure (fonts, layout, navigation, media, charts, controls, replies, 3D, the flair pieces, print, reduced motion and forced colours) and none of the look: no theme token blocks and no theme flourishes. Every body gets the neutral tokens of the none theme, the Bare card above, whatever its data-tk-theme says, so write data-tk-theme="none". A page that links the bare stylesheet and sets no theme resolves to none as well.
Restyle by setting the tokens below on body: components read them, so charts, controls and 3D follow, then add your own CSS. The kit faces (Newsreader, Archivo, Archivo Black, IBM Plex Mono and Kalam) are declared, so a style can name them. tacksheet.py sheet new --bare starts a sheet this way.
Token contract
Every custom property a style or a sheet may set; the manifest lists the same tokens. Theme tokens weigh nothing, so a plain body{…} rule wins over them. The panel tokens and the riso heading inks act only in the riso themes of the full kit.
Styles
A style is a look saved on tacksheet.com: a base (one of the built-in keys riso, riso-night, auto, paper, ink, slate or none), tokens, its own CSS and notes for agents, in numbered versions that never change. Tacksheet serves each version as https://tacksheet.com/styles/<style id>/<version>/style.css. Link it right after the kit stylesheet, keep its base on data-tk-theme and name it with data-tk-style:
A style sets tokens on body{} and adds CSS; it never carries script. The kit ignores data-tk-style; Tacksheet reads it, with the links, to tell which style a sheet wears. A built-in theme needs no link and is named by its key (data-tk-style="riso"); a built-in kit style links its own kit stylesheet (below). The sheet's own <style> blocks still apply and win where they are more specific. Style links need kit 1.1.1 or later, where body tokens beat theme tokens; a style on base none uses the bare kit, new in 1.2.0.
Built-in styles
From 1.6.0 the kit also serves whole looks as built-in styles, each with its own type and fonts. Link one right after the kit stylesheet, keep its base theme on data-tk-theme and name it with data-tk-style. Its knobs are attributes and custom properties it reads: set them on the elements named, or leave them out for the defaults. Tacksheet's Style button, tackit.py style head KEY and sheet new --style KEY write these lines for you.
data-aero-osvista | xp | aqua (body); default vista. Window chrome, title bars, taskbar or dock and menu bar, and the default wallpaper.
data-aero-wallpaperaurora | bliss | bubbles | aqua | harmony (body); default follows the OS (vista aurora, xp bliss, aqua aqua). The desktop behind the windows.
data-aero-skyday | sunset | night | ocean (body or tk-hero); default day. The hero window's scene: blue sky and green hills; a dusk sky with purple hills; stars, a moon and dark hills; underwater with light rays and bubbles.
data-aero-photocoverflow | tint | plain (body or one tk-taped); default coverflow. Pictures on an iTunes Cover Flow stage with a reflection (colours untouched), with the 2006 blue tint, or in a plain white frame.
Broadsheetbroadsheet
A newspaper front page: a nameplate, a banner headline, ruled columns, drop caps and pull quotes.
data-br-paletteprimaries | mono (body); default primaries. primaries = red, blue, yellow and black on newsprint; mono = ink plus one accent (set --br-accent, default #FF4F00; the accent carries dark text so any bright colour works).
--br-a / --br-b / --br-c (body); default #D41F17 / #1336B0 / #FFD200. custom palette: a = hot colour that carries light text (title bar, section 1 numeral, chart series 1), b = deep colour for links and blocks, c = bright highlight that carries dark text. Optional --br-paper, --br-ink, --br-on-a, --br-on-c.
data-br-herowedge | disc | bars | grid | type (body); default wedge. the composition in the hero's right panel; type = no artwork, the title takes the full width.
data-br-geometrysome | lots | none (body); default some. how many shapes appear (hero extras, stat corners, the reply card's red cut). none = pure typographic brutalism.
--br-rot / data-br-rotate0 | 1 | 2 (body); default 1. rotation intensity for the title, numerals, stamp band, clipping, print and ticker band. 0 = everything square.
data-br-toneink | a | c (tk-section); default none. prints one section as a solid block (black, hot colour or yellow) to make it stand out.
data-br-photonatural | halftone (body or one tk-taped/tk-image); default natural. natural keeps a picture's own colours; halftone prints it in high-contrast black and white through a dot screen.
Comiccomic
A comic book: a cover logo, panels in black gutters, Ben-Day dots, caption boxes and speech balloons.
--comic-hero-a / --comic-hero-bany two colours; a is the ray/top/dot tone, b the field (body or tk-hero style); default per variant (sunburst #1d7fd6/#43a7f2).
--comic-hero-image / --comic-hero-positionurl(...) of the sheet's own picture, used with data-hero=image; default none, 50% 50%.
--gc-tile / --gc-sky / --gc-tile-size / --gc-hero-inkyour own tile url(...), the colour under it, its size, and the hero text colour on it (body style); default the starfield.
data-banneron | off (body); default on.
--gc-bannerany short text (body, quoted); default "★☆★ Welcome to my Home Page! ★☆★".
data-keynote-schemeocean | sunset | meadow | graphite | spectrum | mono (body, tk-hero or tk-section); default ocean. Gradient and solid ink for the title's last line, first stat, underline marks, heading full stops, steps, timeline dots, slider, hero bloom and chart series 1. ocean: transport, tech, finance; sunset: warm or urgent news; meadow: growth, sustainability, health; graphite: sober or premium; spectrum: celebratory launches; mono: when colour would distract.
data-keynote-heroglow | stage | rings | plain (body or tk-hero); default glow. Hero background: glow, a grained silk ribbon of light in the scheme colours (soft body, folded crest, a fainter second band) that sweeps across behind the copy; a black stage under a top light; three concentric scheme rings around the copy; nothing.
data-keynote-flowrise | fall (body or tk-hero); default rise. Direction of the glow ribbon. rise: it enters low on the left and leaves off the top right; fall: mirrored, it leaves off the top left. Pick the side that keeps the ribbon away from a long first title line, or alternate between related sheets.
data-keynote-chaptersalternate | dark | light (body); default alternate. Which chapters are black: every second one, all of them (a Pro look), or none.
data-keynote-grounddark | light (one tk-section); default follows data-keynote-chapters. Force one chapter's ground.
data-keynote-motionon | off (body); default on. off keeps the designed still even for visitors who allow motion (dense reference sheets).
Launchlaunch
A modern launch page: dark or white, ruled guides, a gradient-lit hero and bento panels.
data-launch-modenight | day (body); default night. Near-black Linear/Vercel stock, or Stripe's white with navy ink and soft card shadows.
data-launch-arthorizon | chroma | current | arc | eclipse | club | prism | nebula | ribbon | silk | none (body (hero, divider, slip avatar, glows, accent) or one tk-section (a wash of the art behind its top)); default horizon (night), ribbon (day). Which approved look lights the page; also sets the accent, glow colours and chart series 1-2. horizon, arc: calm and technical; chroma, current: fresh green-blue; eclipse, club, nebula: warm or playful; prism: bold; ribbon, silk: bright day pages; none: a pure black page.
data-launch-headsans | serif (body); default sans. Geist headings, or Instrument Serif for editorial, human or announcement-led sheets.
--launch-accentany colour with 4.5:1 on the ground (body (style attribute)); default from the art. Overrides links, focus rings, selection, toggles and slider fill.
Magazinemagazine
A magazine feature: a cover spread, justified columns, drop caps, pull quotes and sidebars.
data-mg-palettedune | lagoon | rose | moss | lapis | dusk | ember | mono (body, or any tk-section / tk-card / tk-hero / tk-image); default dune. the plate colours, drop cap, end mark, marks and chart accent. Match it to the content: lagoon for water and travel, moss for nature and growth, lapis for finance and tech, ember for night and drama, mono for sober topics.
--mg-ground / --mg-1 / --mg-2 / --mg-3 / --mg-4 (same); default #F2E5D9 / #F7CDAA / #DB8A5C / #7F3624 / #FCE6C4. custom palette: ground, light, mid, deep, glow. Take them from the sheet's own photos or brand; keep --mg-3 dark (it colours small labels).
data-mg-shapesun | horizon | bloom | ribbon | eclipse (body (hero), or a tk-section / tk-card / tk-hero / tk-image); default sun on the hero; sections cycle bloom, horizon, eclipse, ribbon; cards cycle horizon, ribbon, sun. the composition of a plate.
--mg-x / --mg-y (same); default per shape (e.g. 64% 50%). where the light (sun, glow, eclipse) sits, so the art leans away from nearby text or toward a subject.
data-mg-grainheavy | soft | none (body); default heavy. grain on the plates.
data-mg-spotoff (body or a tk-section); default on. removes the miniature plate beside section headings.
data-mg-coverspread | stack (body or tk-hero); default spread. spread: the picture or plate takes the left page and the headline sits on the right (phones stack it, set left). stack: the plate above a centred headline, for very short sheets or titles longer than about twelve words.
data-mg-columnsauto | 1 | 2 | 3 (body or tk-section); default auto. text columns on wide screens (phones always read in one), only ever for plain running prose: paragraphs, pull quotes, postcards and taped pictures, never lists, tables, charts, forms or callouts. auto: two columns for three to six paragraphs, one long-read column with marginal pull quotes for seven or more. Use 1 for argument-heavy prose, 3 for a long light run.
data-mg-openerplate (tk-section); default spot art beside the title. a wide plate opens the department, like a part-title page. Use it on one or two major sections, with data-mg-shape to vary the picture.
data-mg-papertint (tk-section); default plain paper. prints the section on a tinted stock in the palette, edge to edge, to mark a sidebar section, an interview or a letters page.
data-mg-numbersgiant | row (body or tk-stats); default giant. giant: the first tk-stat as an oversized number beside a ruled list of the rest (lead with the figure that matters). row: an even ruled row of figures for peers of equal weight.
data-mg-foota short word (tk-section); default none (a plain hairline). printed in the section’s running foot ("docks", "crews").
Studiostudio
A design studio's case study: screen-wide titles, numbered sections and a bento of metrics.
Eleven finished sheets ship with the kit's source in examples/sheets/, and between them they use every element. Each is a pattern worth copying whole. tacksheet.py sheet new --template NAME OUT.html copies one, pinned to this kit, with the files it uses.
Layout
The bones of a sheet. Every snippet below is the live example’s own source; copy it as is.
tk-pagetk-herotk-section
The sheet frame. tk-page is the column: 76% of the screen up to 110rem, never narrower than 43rem, with running text kept to a 46rem measure while pictures, grids, tables and code use the full width (wide for dashboards: 94% up to 140rem). Set --tk-page-width, --tk-page-wide or --tk-measure on body to change them. It sits on a translucent panel in the riso themes; tk-hero carries the headline, dek and byline; each tk-section gets a numbered heading with a deep link.
Up 12% on last year, almost all of it on the waterfront.
Planning team · 3 October 2026
tk-gridtk-card
A responsive grid of cards. Columns collapse to two under 900px and one under 640px; cards that hold charts align to the top.
tk-grid: cols (1–6)
Waterfront
Four docks reopened in May.
Downtown
Flat all year, busiest at 8 am.
Uptown
Two new docks open in spring.
tk-callout
Asides with a rubber-stamped kind. Use decision and risk for the two things a reviewer must not miss.
kind: note | tip | warning | decision | risk
Decision. Add 24 docks at Pier 4 before May.
Rebalancing. The evening van run starts at 5:30 pm until then.
Share links keep their own expiry.
Deleting a version cannot be undone.
Trips count where they start.
tk-statstk-stat
A ruled row of headline numbers. A leading + or − on the delta colours it; good="down" flips the meaning. A sparkline inside a stat sits under the number.
tk-stat: good="down"
184.3k
Trips
+12%
9,420
Members
+640
11 min
Median trip
−1 min
tk-table
Tables from JSON or HTML, with a ruled header that sticks, numeric columns aligned right, sorting and a filter box.
data, sortable, searchable, filter
tk-timelinetk-steps
Two lists with structure: dated events from JSON, and numbered steps from plain list items.
tk-timeline: data
Tack the sheet.
Share the link.
Read the replies.
tk-codetk-kbd
Code with light highlighting (js, python, json, html, css, bash) and a copy button; key chips for shortcuts.
tk-code: language
Tacksheet.on('change', ({ name, value }) => {
console.log(name, value); // every state change
});
Press Shift + Enter to send.
tk-detailstk-figuretk-split
A disclosure for the detail most readers skip, a captioned figure, and a two-column split for content that is genuinely parallel.
tk-details: summary
Comparisons, arithmetic, &&, ||, !, ternaries and min/max/abs/round. No eval.
Before
Thirty-day expiry for every sheet.
After
Kept until deleted; expiry is opt-in.
Navigation
Ways through a long sheet.
tk-toc
Contents from the page’s h2 headings (and any h3 you give an id). Sticky on wide screens with sticky, folded behind a toggle on phones with collapsible.
sticky, collapsible, title
The contents list at the top of this page is a live tk-toc: every section of this page, set in three columns by a line of page CSS. On a phone it folds behind its title.
<tk-toc collapsible title="On this page"></tk-toc>
tk-tabs
Panels behind tabs; each child carries a label. Arrow keys move between tabs.
children: label
Trips grew 12% on last year.
Trips count where they start; test rides are excluded.
Exported from the dock controllers at 06:00 daily.
tk-accordion
Stacked questions that open in place.
children: label
Themes, layout, media, 49 chart types, controls, replies and 3D.
Sending replies and loading same-review files.
tk-pagertk-progress
A multi-page sheet that shows one page at a time, and a reading-progress rule (pinned to the top of this page).
Week 39
Pier 4 ran empty on two evenings.
Week 40
The van run moved to 5:30 pm.
Images
Same-review files load as src="./files/name.jpg" through the shell; these demos inline small drawings instead.
tk-gallery
Image sets in a grid, masonry columns or a carousel. Click any image for the viewer (keyboard, swipe, zoom).
layout: grid | masonry | carousel
tk-compare
Before and after, split by a handle you can drag (or move with the arrow keys).
Pan and zoom a large image. Zoom with the buttons, a pinch or Ctrl and the wheel (around the pointer); then press and drag to pan. A plain wheel scrolls the page; on the focused image the arrow keys pan and + and − zoom.
tk-annotatetk-image
Numbered notes pinned to an image, and a captioned image with a credit.
tk-annotate: data · tk-image: caption, credit
tk-gallery
Masonry keeps each image’s own shape.
layout="masonry"
Reading on a phone
Kit 1.5 is the reader release. Every content image opens a full-screen viewer, times read in the reader’s own locale and time zone, and nothing in the kit stops a reader zooming the page.
The image viewer
Tap or click any image with alt text (or any image in tk-image, tk-figure, a figure, tk-taped, tk-duotone, tk-gallery or tk-lightbox). Previous and next go through every viewable image on the page; the counter says where you are; the caption comes from the figcaption or the alt text. Swipe sideways to turn, swipe down or press Escape to close, pinch or double-tap to zoom. Images in links and buttons, decorative images (alt=""), icons and anything marked data-tk-no-viewer stay put.
img: data-tk-no-viewer (also on any ancestor)
<time datetime>
Write the machine time in datetime and leave the text empty (or the raw timestamp): the kit prints the reader’s own date and time and puts the exact time in a tooltip. Your own words stay. Never print raw ISO 8601 or epoch timestamps in a sheet.
datetime · data-tk-format: datetime | date | time | relative | auto | none
Checklist updated
Due
Last run
Kept as written:
tk-table stack
A wide table scrolls sideways, with a shade on the side that has more. With stack, each row becomes a card on a phone, every value under its column name.
tk-table: stack
Stop
Stands
Peak use
Walk to ferry
Owner
Pier 4
32
94%
4 min
Harbor Line
Ferry Plaza
28
88%
1 min
City
Market & 3rd
18
71%
9 min
City
Write for a phone
What the kit cannot do for you.
Everything a reader taps is at least 44 px; a sticky bar is one row, 56 px at most, on a phone.
Link words, not bare URLs or file paths; never hide details until a pointer rests on something (a phone has no pointer to rest).
A level-1 tape title of about six words; stamps of a word or two, three or fewer on a sheet.
Charts as tk-chart: an inline SVG with its own text shrinks that text to a few pixels on a phone.
Times as <time datetime>, never raw timestamps.
Video, audio, slides, scrolly
Time-based media and scroll-driven stories.
tk-video
A video with chapters (the current one is marked), playback speeds and timestamp links that seek. Nest <track> for captions.
A designed audio player with a seek bar and chapters.
title, src, chapters
tk-slides
A short deck inside the sheet. Arrow keys work when it has focus.
Tack
Every upload is a version; nothing is overwritten.
Share
Links work without an account and expire only if you say so.
Reply
Replies record the version they answer.
tk-scrolly
A scroll-driven story: the chart named by for parks in a sticky column and each step’s JSON filter is applied as it reaches the middle of the screen. Axes stay fixed, so the chart builds instead of rescaling. Inside Tacksheet the page scrolls, not the sheet: the shell reports its window and the chart follows the step being read, pinned to the top on a phone.
for · step: {"filter": "…"}
Winter
Downtown leads by 2,800 trips.
May
The seawall docks reopen and the waterfront passes downtown.
The year
The waterfront ends 11% ahead.
Charts
One element, <tk-chart type data x y series>, and forty-nine types on a single mark system: thin lines with ringed markers, bars with a rounded data end, hairline grids, labels in text colours and a legend only for two or more series. Every chart carries a data table behind “View data”. The options attribute deep-merges raw ECharts JSON for anything else.
line{labels, datasets:[{label, data}]} or rows with x/y/seriesareaSame as line; fills the first seriesstacked-areaSame as line, 2+ seriesbarSame as linehorizontal-barSame as line; long labels read left to rightstacked-barSame as line, 2+ seriesgrouped-barAlias of barpercent-barStacked to 100%scatter{points:[[x,y]]} or rows with x/ybubble{points:[[x,y,size]]} or rows with x/y/zhistogram{values:[…]}box{groups:[{name, values}]}violin{groups:[{name, values}]}heatmap{x, y, values:[[xi, yi, v]]}calendar-heatmap{values:[[date, v]]}treemap{children:[{name, value, children?}]}sunburstSame as treemapsankey{nodes:[{name}], links:[{source, target, value}]}network{nodes, links, categories?}chord{labels, matrix:[[n]]}Approximation. A circular graph: no arc ribbons, no direction; link width is √value.radar{indicators:[{name, max}], wide series}polar-barWide single seriescandlestick{x, values:[[open, close, low, high]]}ohlc is an approximation. It draws these same candles, not tick bars.waterfall{labels, values:[deltas…, total]}funnel[{name, value}]gauge{value, min?, max?, name?, unit?}pie[{name, value}]donut[{name, value}]rose[{name, value}]sparkline{values:[…]}bullet{value, target, ranges, label?}dumbbellrows with a/b or {labels, pairs}slopeWide series; first two labelslollipopWide single seriesparallel-coordinates{dimensions, data:[[v…]]}themeriver{values:[[time, value, name]]}timeline{tasks:[{name, start, end}]}choropleth · usa{values:[{name, value}]}choropleth · world{values:[{name, value}]}dot-map · usa{points:[{name, lon, lat, value?}]}contour{points:[[x,y]]}Approximation. A smoothed density heatmap with hidden axes, not isolines.error-barsrows with y/errorregression{points:[[x,y]]}comboWide series; kinds/axesbarfacet="column" on rows data
Dynamics
State is a set of named values. Controls write it; charts, text, tables and tk-show read it through a small expression language with no eval.
Named state and controls bound to it. The chart, the sentence, the callout and the table below all follow the controls; no page code. The slider and Replay drive the same value, so a replay builds the chart year by year (year<=upto), the slider follows it, and moving the slider stops it.
controls: bind, label, value · consumers: filter, template, when, expr
The West at full range: 71 adopters in 2024.
tk-radio-grouptk-rangetk-searchtk-numbertk-date
The other five controls, styled to match: a segmented radio group, a two-handle range, search, number and date fields.
Every widget talks to the shell over the tacksheet-sheet-v1 bridge. Opened anywhere else they render disabled with “Open this sheet in Tacksheet to reply”, which is what you see here.
From 1.6 a reader’s unsent answers are kept in their own browser while they type: a reload or a restart brings them back with “Restored your unsent answers · Start over”, and they are dropped once the reply is sent. The sheet never stores them itself; Tacksheet’s page around it keeps them on that device only and never sends them anywhere. Give a widget an id so its answers stay with it when you move it on the page.
Answers beside the contentform=
From 1.7 each question sits right under what it asks about and still goes back as one reply. Give each widget form="review" and a name, and end with <tk-form id="review">. The widgets answer in place without a Send button of their own and each says whether it is answered; the form counts them (“2 of 3 answered”), lists the open ones (each one jumps there) and sends every answer through one Send box. A required answer left open is marked where it sits, and Send takes the reader there. Unsent answers come back after a reload, as one draft. The reply carries each answer as a field: tackit.py sheet schema sheet.html writes the matching --form schema, or publish with --form-from-sheet.
<tk-section id="plan">
<h2>The plan</h2>
<p>…</p>
<tk-question form="review" name="clear" type="scale" required question="How clear is the plan?"></tk-question>
</tk-section>
<tk-section id="examples">
<h2>Examples</h2>
<tk-vote form="review" name="next" question="Which example should grow next?" options="#next-options"></tk-vote>
</tk-section>
<tk-feedback form="review" name="else" title="Anything else?"></tk-feedback>
<tk-form id="review" title="Send your review" submit-label="Send review"></tk-form>
tk-feedbacktk-question
Free text and single questions: choice, scale, rating, yes/no or text. Outside Tacksheet they render disabled with a note; inside, Tacksheet asks the reader once in its own Send box, then sends. A reply holds up to 4,000 characters (the box stops there) and is never shortened.
A multi-field form rendered from the same schema the server uses: text, long text, numbers, booleans, single and multiple choice, nested groups.
schema, title, form-id, submit-label
tk-request-infotk-annotate-feedback
Ask for more on a section, or pin a comment to a point on an image.
tk-request-info: section · tk-annotate-feedback: src, alt
Canvases, prototypes and strips
From 1.8 a sheet can hold content that is wider than the page, on purpose and in its own box: the page itself still never scrolls sideways. Use a prototype canvas for a click-through flow (an app or a website someone should try), a board for things to arrange (a flow map, a sticky-note review, options side by side), and a strip for content that is a row by nature (columns of a board, a timeline, before and after). Everything else stays a normal top-to-bottom sheet.
tk-canvas starttk-screentk-hotspottk-task
A prototype: screens of HTML, SVG or images on a canvas, with start naming the first. Buttons with data-tk-go="screen id" glide the camera to the next screen, and an arrow is drawn for each link; data-tk-back goes back; tk-hotspot makes a tappable rectangle on a picture. A prototype is locked: readers pan and zoom, tap through, and press Simulate to try it one screen at a time over a dimmed page, always from the start screen. The page’s look never reaches inside a screen: style it with your own CSS, scoped under tk-screen. data-tk-set, data-tk-show and data-tk-pressed-when keep a little state, so choices carry to the next screen. A tk-task asks a reader to reach a goal screen and sends what happened as one reply.
tk-canvas: width, height, start, label, arrows · tk-screen: x, y, name, device, width, height · tk-hotspot: x, y, w, h, label · inside screens: data-tk-go, data-tk-back, data-tk-set, data-tk-show, data-tk-default, data-tk-pressed-when · tk-task: for, goal, name
Rent a bike for 30 minutes.
Pier 4
7 bikes · 2 min walk
Classic bike$0.20 a minute
How long?
You can return it early.
Bike 4127 is unlocked
Ride for 15 minutes.
tk-canvastk-notetk-link
A board: a canvas without start. Any child with x and y is placed there (a card of your own, an image, a tk-note), and tk-link draws an arrow between two of them. Readers press and hold (or drag with a mouse) to move anything; the arrows follow and Reset layout puts it all back. On your own page a Save button keeps the new layout, as this version or a new one (Tacksheet asks which); for everyone else moves stay on their screen. Where replies are on, readers add notes of their own with + Note and send them to you as one reply, each with the item it sits nearest. On a phone a canvas waits for Tap to explore, so it never takes the page’s swipes. Give each item an id so a saved layout can find it.
tk-canvas: width, height, label, arrows, notes · any item: x, y, w · tk-note: x, y, w, color · tk-link: from, to, label · --tk-canvas-height
Soft launch
Ten percent of readers first, everyone by Friday.
Waitlist
Invite in batches of fifty a day.
Open doors
Everyone on day one, support on standby.
Waitlist worked last time. Fewer support tickets.Risky before the billing fix ships.
tk-strip
One row that scrolls sideways inside the sheet: each child is a card. Swipe, Shift+wheel, the arrow buttons or the arrow keys move it, and “1–3 of 5” says where the reader is. snap lands each swipe on a card; item-width sets the card width.
label, snap, item-width · --tk-strip-item
Ideas
Group rides for teams
Dock wait-list
Planned
Reserve a bike for later
In progress
E-bike battery on the map
Testing
Ride length picker
Apple Pay
Shipped
Return to any dock
3D
Models load from the pinned sample (src="sample.glb"), a same-review ./files/ name through the bridge, or inline base64 with glb="…" (or glb="#id" to embed once and reuse). Lighting, sky, fog, floor and terrain colours come from the theme.
tk-model
A GLB viewer in a lit studio: soft key, fill and rim lights, reflections, a contact shadow and a fading floor grid, framed to fit. Orbit with hotspots that fly the camera; auto-rotate turns it on a turntable.
Path mode glides along authored keyframes (a spline, eased into every view), with a scrubber and chapters. Fly and walk modes add WASD and a touch stick; walk follows a terrain heightfield.
Data-driven 3D from JSON: terrain heightmaps (smoothed, coloured by elevation, with contours), meshes, lines and point clouds. Colours follow the theme.
title · data: {terrain, meshes, lines, points, pointcloud, camera, grid}
Riso flair
Paper, tape and ink pieces that make a sheet look printed and pinned rather than generated. They work on every theme and look most at home on riso and riso-night. Each is one element with a few attributes, no network and no images of its own beyond the kit's textures.
Compose like a pinned, printed sheet
Print in one or two inks and keep them: cobalt for type, vermilion for marks, plum where they overprint. Sunflower and cornflower are the night stock's inks.
Tape the title and the images. Leave charts, tables and cards flat on the stock; one taped thing per screen is plenty.
Stamp a state once, next to what it describes. Put a slip beside the paragraph it answers, not in a sidebar.
Highlight a phrase, not a paragraph. Use a torn strip to change subject, not between every section.
Avoid generic gradients, glass, glows and rows of identical rounded cards. Texture is the point, and legibility comes first: the kit checks tape-title contrast for you, and every other piece keeps the page's text colours.
tk-tape-title
A real heading printed on washi tape: translucent film with torn ends, fibre and a faint pattern, each line on its own strip. The kit measures the tape as it lands on the page and prints the title in an ink that clears 4.5:1 (3:1 at 24px and up). An ink that fails is replaced by cobalt, near-black or cream, with a console warning; if none clears a patterned film, the film prints solid. The last title below asks for black on cobalt and gets cream.
Where trips startNotes from the crewAsks for black, prints in cream
tk-taped
Tapes its content to the sheet. An image becomes a print with a cream border (it stays cream on night stock); anything else is taped as it is with frame="none". A caption is handwritten on the print.
A rubber stamp in one ink, worn by the pad and struck at an angle (it thumps in once, unless the reader prefers reduced motion). Preset words carry their own ink; your own word goes inside. Screen readers hear "Approved stamp, 3 Oct 2026".
kind: approved | draft | rejected | final | shared | seen · ink: cobalt | vermilion | plum · shape: box | double | round · date · tilt
Proof 2
tk-slip
A torn, ruled slip of cream paper with a ballpoint note, held by a strip of tape. On wide sheets float moves it into the margin beside the paragraph it answers. It is never a yellow square sticky note.
tape (or none) · from · float: left | right · tilt
Pier 4 ran empty on 31 evenings. Can the 6 pm rebalance start at 5?
tk-mark
Emphasis in riso ink: a highlighter swash, a wavy underline or a ring around a word. The words keep the page's text colour (dark type on the light highlight on night stock), so they keep their contrast.
Ridership grew 12% on last year, and docks at Pier 4 ran empty on 31 evenings, mostly on weekends.
tk-torn
A ragged strip of torn paper between parts of a sheet. Text inside prints on the strip in an ink that passes on that paper; screen readers hear a separator with that label.
Prints an image in two inks on cream paper: the first carries the shadows, the second lands a little off register over the midtones and overprints where they meet. halftone screens the second ink into dots. Give the image its alt text as usual.
inks: two of cobalt, vermilion, plum, sunflower, cornflower, black · offset (px) · halftone · caption · credit
tk-overprint
Display words or numbers printed twice, the second pass off register. Named inks switch to their night shades on riso-night. Screen readers read the words once.
inks: two named inks · offset (em, 0 to 0.15)
184,300
trips this quarter, up 12%.
Your own code
Agents may add their own CSS, scripts and custom elements alongside the kit. Build DOM with createElement and textContent, read the tokens in CSS, and use the API below. See examples/sheets/custom-component.html for a full component.
Tacksheet.state.get(name) / set(name, v) / all()Read and write named state; every bound element updates.
Tacksheet.on('change', fn)Called with {name, value, values} after every state change. Also 'theme'.
Tacksheet.chart(node)Re-render one tk-chart after you change its data block.
Tacksheet.theme()The resolved theme: palette, colours, faces and 3D tokens.
Tacksheet.file(name)A Promise of a same-review file as a Blob, through the bridge.
Tacksheet.expr(text)The safe expression evaluator used by filters and templates.
Tacksheet.define(name, class)Register a custom element once, after the kit is ready.
Tacksheet.readyA Promise that resolves when the document is parsed.
Signatures
tk-signature
Paste the create-request API embed, including its application/json child and request attribute. The trusted sheet shell opens private signer links and reads status. Each bearer link grants signing access; include it only on a private sheet for intended signers. The completed document can be saved as a PDF.
request
Bridge protocol
Sheets run in an opaque-origin sandbox, so messages arrive with origin "null": the shell checks event.source, and so does the sheet. Every message carries both kind and type. The kit always sends resize so the shell can size the frame.
Sheets cannot fetch (connect-src 'none'), so same-review files (./files/…) arrive through file.fetch: the kit asks by bare name, the shell transfers the ArrayBuffer, and the kit turns it into a blob: URL for media or parses it for tk-model.
Manifest and agent prompt
manifest.json lists every element, attribute, chart type and data shape, theme, file hash and bridge message. The recommended agent prompt:
Build a Tacksheet sheet: one HTML file with
<link rel="stylesheet" href="https://tacksheet.com/kit/1.8.0/tacksheet.css"> and
<script src="https://tacksheet.com/kit/1.8.0/tacksheet.js" defer></script>,
body data-tk-theme="riso" (riso-night for dark stock, auto to follow the
reader; paper, ink and slate also work). For a one-off look, link
tacksheet-base.css instead, set body data-tk-theme="none" and the kit tokens
on body (`tacksheet.py sheet new --bare` starts one). For a saved style,
link its style.css after the kit stylesheet and name it with data-tk-style.
Use tk-* elements (see the
manifest for names and attributes), inline data as
application/json scripts, and validate with `tacksheet.py sheet validate`
before publishing. Long-form rows need series="..." (see long-data.html).
Put sparklines inside tk-stat. Load sibling files as src="./files/<name>"
(the file.fetch bridge); models under ~1 MB can ride inline as glb="..."
or glb="#id". Drive scrolly with <tk-scrolly for="<chart-id>"> plus one
JSON {"filter": "..."} per step, cumulative (n <= 3, n <= 6, ...). Custom
components go through Tacksheet.define (see custom-component.html).
Compose like a pinned, printed sheet: one or two inks, a tk-tape-title for
the title, tk-taped for images, a tk-stamp for a state, a tk-slip for an
aside; no generic gradients or rows of identical cards.