# About Source: https://teasim.com/about.md ## Why we exist Most component libraries make two decisions on your behalf. They decide how your site looks, by shipping a stylesheet. And they decide how your site is built, by rendering your content for you. Both are borrowed decisions, and both are expensive to undo two years later when you want something they did not anticipate. Teasim tries to make neither. ## How the split works `@teasim/astro` renders markup and carries behaviour with it. It ships no stylesheet at all; your own TeaCSS build generates exactly the classes your markup used. `@teasim/aster` arranges those elements into the page-level chrome a site repeats — the shell, the header, the navigation, the table of contents. It owns arrangement and never restates a surface. `@teasim/astra` computes navigation, validates content, and serves the machine-readable routes. It renders nothing. No package knows what the others look like, and a project can take any of them on its own. ## What we optimise for **The markup you would have written.** An accordion is a `details` element. If you inspect the DOM and find something you would not have written by hand, that is a defect. **Behaviour without JavaScript first.** The open state belongs to the browser. A runtime binds on top when there is one, and the page works when there is not. **Decisions that are written down.** Where something looks like removable duplication and is not, the reason lives beside it, so the next reader does not have to rediscover it by breaking something. --- # Acceptable use policy Source: https://teasim.com/acceptable-use-policies.md ## 1. Scope This page would govern use of a hosted service. Teasim publishes packages rather than running a service, so nothing here restricts anything today. It exists to demonstrate the policy template. ## 2. The software Use of the packages is governed by their MIT licence, which permits commercial use, modification, distribution and private use, and which asks only that the copyright notice travel with the code. ## 3. What a policy of this kind usually covers A real acceptable use policy names the conduct a service will not host: attempting to breach its security, interfering with other users, automated access that degrades it for everyone, and distributing content the operator is required to remove. ## 4. Reporting A hosted service names a route for reporting abuse and commits to a response window. See the [DMCA policy](/dmca-policy) for the takedown-specific version of the same shape. ## 5. Enforcement A real policy states what happens when it is breached, and in what order — notice, suspension, termination — so the consequences are predictable rather than discretionary. --- # Approach Source: https://teasim.com/approach.md ## Start from the markup Design the DOM before the API. If the element you would have written by hand is a `details`, the component is a `details`. Props exist to arrange markup, not to hide it. ## Measure instead of reasoning Where a question has a number attached, get the number. Whether a rebuild is redundant is a hash comparison, not an argument. Whether a role belongs on an element is a count of how many appear on a page. Whether a colour passes is a pixel read back off a canvas with the whole ancestor stack composited onto it. Reasoning is what you do when measurement is impossible, not when it is inconvenient. ## Verify the thing that ships A green build proves the code compiles. It proves nothing about what a browser does with the output. After a component change, open the page. The corollary is that verification has to be *hostile*: run the check against a case whose answer you already know, and if it agrees, then trust it on the cases you do not. ## Let the gate do the blocking A check you read with your eyes is a report. If it is meant to block, the exit code has to be the condition. ## Write down what you decided not to do A decision that produces no diff leaves no trace. The next review will reach the same question, spend the same time, and may answer it differently. Where it matters, a test pins the current shape and names the reason in its title. ## Prefer the boring failure Given two designs, prefer the one whose failure is loud. A class name assembled from a template literal produces no CSS and no error — the markup looks correct and the element is simply unstyled. A literal string that is wrong produces a rule you can see in the stylesheet. --- # Attributions Source: https://teasim.com/attributions.md ## The element package takes none `@teasim/astro` has no external runtime dependencies, and neither does `@teasim/aster`, whose only peer is `@teasim/astro` itself. Where a browser primitive exists it is used directly — `details` for disclosure, `dialog` for modals, `popover` where it is supported. Where one does not, the behaviour lives in a small shared runtime inside the package. This is a constraint, not an accident, and it is checked: the dependency boundary is that the element package takes nothing and the content package may, so where a feature needs a dependency, that need decides which package it belongs in. ## What the site uses **Astro** builds the site and owns the content collections, the markdown pipeline and the view transitions. **TeaCSS** generates the stylesheet by scanning source for whole class tokens. Every utility on this site is a class in the markup; nothing is written by hand except a handful of gradients that a utility cannot express. **Shiki**, through Astro's own markdown pipeline, highlights code at build time. The components frame the result and never tokenize it themselves. **IBM Plex Sans** and **IBM Plex Serif**, self-hosted at build time. ## Standards The interface targets WCAG 2.2 AA. The criteria that shaped the most decisions here were 1.4.10 Reflow, 1.4.11 Non-text Contrast, 1.4.12 Text Spacing, 2.4.1 Bypass Blocks, and 2.5.8 Target Size. --- # Brand Source: https://teasim.com/brand.md ## The frame Every band on this site sits inside a rail on each edge. The rail is `clamp(1rem, 18.75vw - 6rem, 3rem)` wide, so it grows with the viewport and stops at 48 pixels, and it always draws the line at its inner edge. Above 1440 pixels a gutter opens between the rail and the content column and draws a second line at its own inner edge — 1440 because that is the first width where the gutter is wide enough for the two lines not to read as one thickened rule. The content column is capped at 80rem. Everything else is margin. The gutter absorbs whatever is left over, which is what keeps the two sides symmetric rather than piling the slack against one edge. ## Corner nodes Where two bands meet, a six-pixel square rotated 45 degrees marks the corner, offset so its centre sits on the seam. That only holds because of one invariant: every frame row draws the boundary as a border on its own bottom edge, and the last row on the page draws none. A border moves the row's padding box by the same pixel the node is offset against, so the node lands on the line without knowing anything about the row on the other side. A line drawn by anything else — an absolutely positioned hairline, a border on content nested inside the row — misses by one pixel, every time. They are decoration, hidden from assistive technology, and they are the one flourish the system allows itself. ## Lines carry the structure There are three kinds of line and they are not interchangeable: - **Rails and seams** are full-strength `gray-500`, painted as real borders. They are structure, and they must survive forced colours — which a border does, and which the seam names explicitly anyway. - **Hatching** is a 135-degree repeating gradient, one pixel of ink every five. It marks a major division between sections. - **Wisps** are alpha tints at around 28%. They are ornament. They are allowed to disappear under forced colours, because a deliberate whisper repainted at full contrast is not the same design. ## Type IBM Plex Serif sets every heading, at weights between 340 and 400 — light enough that a large heading does not shout. IBM Plex Sans sets body text. The mono face carries eyebrows, metadata and code, always uppercase with wide tracking when it is a label. ## Motion One transition, 150 milliseconds, ease-out, on colour and small translations. The page frame scales fractionally when a sheet opens over it. Everything stops under `prefers-reduced-motion`. --- # Contributor agreement Source: https://teasim.com/cla.md ## 1. Scope The packages are MIT-licensed. Contributions are accepted under the same licence, which for most projects of this size makes a separate signed agreement unnecessary. ## 2. What you confirm by contributing That you wrote the contribution or have the right to submit it, and that you are willing for it to be published under the project's licence. Where an employer owns your work, that permission has to come from them rather than from you. ## 3. What a contribution includes Code, tests and documentation together. A change that alters behaviour without a test that would fail on the old behaviour is incomplete, and a change that alters a documented contract without updating the document is also incomplete. ## 4. Review Contributions are reviewed adversarially: the reviewer's job is to try to refute the change rather than to approve it. That is not a comment on the contributor — it is the only way to catch decisions that no test can fail. ## 5. What is not accepted Dependencies in the element package. It takes none by design, and a contribution that adds one belongs in the content package or nowhere. --- # Accordion Source: https://teasim.com/components/accordion.md ## Installation The open state is `
` and ``, so a row opens before any JavaScript runs and keeps working if none ever does. A delegated runtime binds on top to add what the element has no answer for — arrow-key roving focus, `multiple`, `defaultValue`, and an `accordion:change` event. There is no client directive to write; it binds itself and survives a view transition. ## Preview

Does this need JavaScript?

No. The disclosure is the browser's, and so is the exclusive-group behaviour when items share a `name`.

Where do the styles come from?

Your application's TeaCSS build. The package ships no stylesheet — `accordionRecipe` names the classes and your entry generates them.

Can I restyle it?

Every part takes `class`, and caller classes merge last. ```astro --- import { Accordion, AccordionContent, AccordionItem, AccordionSummary } from "@teasim/astro/accordion"; ---

Does this need JavaScript?

No. The disclosure is the browser's.

Where do the styles come from?

Your application's TeaCSS build.
``` ## Examples ### One at a time Give sibling Items the same non-empty `name` and the browser closes the open one when another opens. The group name is **document-wide**, so a page with two independent accordions needs two names.

Green

Two minutes, water just off the boil.

Black

Three minutes, water at a rolling boil. Open at most one member of a named group initially — two Items sharing a name and both carrying `open` is a state the browser has to resolve, and it closes one of them for you. ### Several at once Dropping `name` is not enough. `multiple` defaults to `false`, and on bind the runtime keeps the first open Item and closes the rest — so two Items that both carry `open` render as one open row. Pass `multiple` on the **root** to keep them all.

Ships no stylesheet

The application's TeaCSS build generates every rule.

Ships no client JavaScript

For this family the open state is native; the runtime only adds focus and events. ```astro ``` The two mechanisms are independent and both apply. `name` is the browser's, and it works with scripting off; `multiple` is the runtime's, and it wins on bind. Setting `multiple` while siblings share a `name` gets you the browser's exclusivity anyway — use one or the other, not both. ## API reference Four components, each rendering one native element. | Component | Renders | Own props | | --- | --- | --- | | `Accordion` | `
` | `multiple`, `defaultValue`, `collapsible`, `orientation`, `loopFocus` | | `AccordionItem` | `
` | `value` — plus native `open` and `name` | | `AccordionSummary` | `` | none | | `AccordionContent` | `
` | none | ### Accordion | Prop | Type | Default | Notes | | --- | --- | --- | --- | | `multiple` | `boolean` | `false` | Several Items open at once | | `defaultValue` | `string \| string[]` | — | Which `value` starts open; an array joins with commas | | `collapsible` | `boolean` | `true` | The last open Item may close | | `orientation` | `"horizontal" \| "vertical"` | `"vertical"` | Which arrow keys move focus | | `loopFocus` | `boolean` | `true` | Focus wraps at either end | ### AccordionItem | Prop | Type | Default | Notes | | --- | --- | --- | --- | | `value` | `string` | — | Identifies the Item to `defaultValue` | | `open` | `boolean` | `false` | Native `
` attribute | | `name` | `string` | — | Native group name; siblings sharing one are mutually exclusive | `AccordionSummary` must be the **first direct element child** of its Item — that is a `
` requirement, not this component's. `AccordionContent` renders a plain `
` and does not force a region landmark. Each component owns `aria-controls`, `aria-disabled`, `aria-expanded` and `role`, and `AccordionSummary` owns `tabindex` as well; passing any of them is ignored rather than merged. Everything else native passes through. ### How the props reach the runtime The five root props are not read by the server half. `Accordion` writes each as a `data-*` attribute — `data-multiple`, `data-default-value`, `data-collapsible`, `data-orientation`, `data-loop-focus` — and the runtime reads them from the DOM on bind. That is why the markup is complete before any script runs — the options travel as markup, not as a hydration payload. The runtime itself is **not public API**. `@teasim/astro/accordion` exports the four components and nothing else; the browser half is loaded by the component's own `