{"id":44073708,"url":"https://github.com/homebound-team/truss","last_synced_at":"2026-04-01T19:30:33.803Z","repository":{"id":38130283,"uuid":"272137093","full_name":"homebound-team/truss","owner":"homebound-team","description":"A TypeScript DSL for writing utility CSS in React/JSX","archived":false,"fork":false,"pushed_at":"2026-03-26T04:39:18.000Z","size":4250,"stargazers_count":32,"open_issues_count":8,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-03-26T09:55:04.354Z","etag":null,"topics":["atomic-css","critical-css","css","dsl","emotion","fela","jsx","react","typescript","utility-css"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/homebound-team.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-06-14T05:08:48.000Z","updated_at":"2026-03-26T02:12:48.000Z","dependencies_parsed_at":"2023-02-18T15:30:54.542Z","dependency_job_id":"58ae8a86-9bf0-4afd-8cfa-7b3bb9da39ff","html_url":"https://github.com/homebound-team/truss","commit_stats":null,"previous_names":[],"tags_count":119,"template":false,"template_full_name":null,"purl":"pkg:github/homebound-team/truss","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/homebound-team%2Ftruss","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/homebound-team%2Ftruss/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/homebound-team%2Ftruss/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/homebound-team%2Ftruss/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/homebound-team","download_url":"https://codeload.github.com/homebound-team/truss/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/homebound-team%2Ftruss/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31291143,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T13:12:26.723Z","status":"ssl_error","status_checked_at":"2026-04-01T13:12:25.102Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["atomic-css","critical-css","css","dsl","emotion","fela","jsx","react","typescript","utility-css"],"created_at":"2026-02-08T06:07:15.895Z","updated_at":"2026-04-01T19:30:33.792Z","avatar_url":"https://github.com/homebound-team.png","language":"TypeScript","readme":"\u003cp align=\"center\" style=\"padding: 100px\"\u003e\n  \u003cimg src=\"logo.svg\" width=\"400\" /\u003e\n\u003c/p\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"https://img.shields.io/npm/v/@homebound/truss\" /\u003e\n  \u003cimg src=\"https://circleci.com/gh/homebound-team/truss.svg?style=svg\" /\u003e\n  \u003chr /\u003e\n\u003c/div\u003e\n\nTruss is a TypeScript DSL for writing utility CSS (think Tailwinds or Tachyons) in React/JSX.\n\n## Quick Example\n\nWriting Truss code looks like:\n\n```tsx\nimport { Css } from \"/src/Css.ts\";\n\n\u003ch1 css={Css.f24.black.$}\u003eTruss v2\u003c/h1\u003e\n\n\u003cp css={Css.bodyText.$}\u003eThis demo uses the Truss DSL.\u003c/p\u003e\n\n\u003cdiv css={Css.df.gap1.$}\u003e\n  \u003cdiv css={Css.p1.ba.bcBlack.br2.cursorPointer.onHover.bcBlue.bgLightGray.$}\u003e\n    Border box with padding and radius\n  \u003c/div\u003e\n  \u003cdiv css={Css.bgBlue.white.p1.br2.cursorPointer.onHover.bgBlack.$}\u003e\n    Blue background with white text\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\nWhich our Vite/esbuild plugins compile to production HTML output:\n\n```html\n\u003ch1 class=\"f24 black\" data-truss-src=\"App.tsx:8\"\u003eTruss v2\u003c/h1\u003e\n\n\u003cp class=\"f14 black\" data-truss-src=\"App.tsx:10\"\u003eThis demo uses the Truss DSL.\u003c/p\u003e\n\n\u003cdiv class=\"df gap1\" data-truss-src=\"App.tsx:12\"\u003e\n  \u003cdiv class=\"pt1 pb1 pr1 pl1 bss bw1 bcBlack h_bcBlue br2 cursorPointer h_bgLightGray\" data-truss-src=\"App.tsx:13\"\u003e\n    Border box with padding and radius\n  \u003c/div\u003e\n  \u003cdiv class=\"bgBlue h_bgBlack white pt1 pb1 pr1 pl1 br2 cursorPointer\" data-truss-src=\"App.tsx:16\"\u003e\n    Blue background with white text\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\nAnd a static, build-time generated CSS file:\n\n```css\n.df { display: flexl };\n.black { color: black };\n.pt1 { padding-top: 8px };\n.bcBlack: { background-color: black };\n.h_bcBlue:hover { background-color: blue; }\n// etc...\n```\n\n## Quick Features\n\n- Inline CSS-in-JS that is build-time compiled to a single static CSS stylesheet:\n  - `\u003cdiv css={Css.mt1.black.$}\u003e` -\u003e `\u003cdiv class=\"mt1 black\"\u003e`\n  - Zero runtime overhead for static styles 🚀\n  - Vite plugin emits a single `truss-(contenthash).css`, for optimal caching and performance\n  - Homebound's main 400k LOC React SPA has a 100kb uncompressed `truss.css` file\n\n- Naturally use dynamic values:\n  - `Css.mt(someValue).$` or\n  - `Css.bgColor(maybe ? Palette.Black : Palette.Blue).$` or\n  - `Css.mt0.if(someCondition).mt4.$`.\n  - Still compiled to static/atomic CSS, with a lightweight runtime helper to apply dynamic values\n\n- Pseudo-selectors and breakpoints:\n  - `Css.white.onHover.black.$` or\n  - `Css.ifSm.mx1.$`\n  - Intentionally limited to the styling the immediate element\n  - See the \"Pseudo-Selectors\" section below for rationale \u0026 escape hatches\n\n- `Css` expressions are \"just POJOs\", so naturally amenable to composition\n  - `\u003cdiv css={{ ...Css.mt2.$, ...(someCondition ? Css.bgRed.$ : Css.bgGreen.$) }} /\u003e`\n  - The last-set value _per property_ wins, i.e. not \"the last class name\"\n  - Extremely natural to build up complex styles with conditionals, view logic, etc.\n\n- Tachyons-inspired abbreviations for superior inline readability\n  - No long class names that compound into a \"wall of text\"\n  - No IDE plugins needed to make your JSX readable again 😅\n  - Consistent `FooBar` -\u003e `fb` abbreviation pattern:\n    - `justify-content: flex-start` is `jcfs`,\n    - Easier to memorize/read\n  - See [Why Tachyons](#why-tachyons-instead-of-tailwinds)\n\n- Configure your design system in Truss's configuration 🧑‍🎨\n  - Color palette, fonts, increments, and breakpoint 🎨s\n  - [See example config](https://github.com/homebound-team/truss/blob/main/packages/template-tachyons/truss-config.ts) and the \"Customization\" section below\n\n- Escape hatch to arbitrary/runtime selectors\n  - `useRuntimeStyle({ body: Css.blue.$ })`\n  - Only applied when the component is mounted\n\n- Type-checking built in 💪\n  - No editor support or IDE extensions required for great DX\n  - Just regular TypeScript (...with code-generation \u0026 build-time Vite plugins)\n\n- Why not Tailwinds?\n  - Our abbreviations are shorter 🩳\n  - Composing styles property-by-property with POJO spreads instead of class name strings is more natural and less error-prone\n  - Easier escape hatches to dynamic values \u0026 dynamic selectors\n  - We just like Truss better 🤷 😀\n\n- Why not StyleX?\n  - StyleX arrays for composition instead of objects, which did not work for our legacy Truss v1 codebases\n  - Too strict with no escape hatches for dynamic/transient CSS/selectors, which are rare but still occur\n  - ...but we heavily crib StyleX's atomic CSS priority system 🙏\n\nAlso see the \"Why This Approach?\" section for more rationale.\n\n## Quick How It Works\n\nTruss uses your project's `truss-config.ts` to generate a `src/Css.ts` file with the configured abbreviations/design system in a TypeScript DSL.\n\nThis file exports a `Css` symbol that you use like:\n\n```typescript\nimport { Css } from \"src/Css\";\n\nconst css = Css.mx2.black.$;\n```\n\nWhere `Css.` signals \"the start of your CSS expression\", and `.$` signals \"the end of your CSS expression\".\n\nIn between, you can chain as many abbreviations/methods as you want, and they will all be statically typed and compiled into atomic CSS classes at build time.\n\nThese expressions are rewritten to be \"just plain objects\":\n\n```typescript\n// Input\nconst css = Css.mx2.black.$;\n// Output\nconst css = { marginLeft: \"ml2\", marginRight: \"mr2\", color: \"black\" };\n```\n\nWhen every value in the expression is static (no runtime variables or conditionals), the plugin resolves the class names at build time with zero runtime overhead:\n\n```tsx\n// Input\nreturn \u003cdiv css={Css.df.aic.black.$}\u003econtent\u003c/div\u003e;\n// Build-time output — no runtime call, just a plain className\nreturn \u003cdiv className=\"df aic black\"\u003econtent\u003c/div\u003e;\n```\n\nWhen the expression contains dynamic values, a lightweight `trussProps` runtime helper is used:\n\n```tsx\n// Input\nreturn \u003cdiv css={Css.mt(someValue).black.$}\u003econtent\u003c/div\u003e;\n// Build-time output\nreturn (\n  \u003cdiv {...trussProps({ marginTop: [\"mt_var\", { \"--marginTop\": __maybeInc(someValue) }], color: \"black\" })}\u003e\n    content\n  \u003c/div\u003e\n);\n```\n\n## Installation\n\nFor web usage, use the `truss` command to generate the `Css.ts` (and `Css.json` metadata file) from your `truss-config.ts`:\n\n- `npm i --save-dev @homebound/truss`\n- Add a `truss` command to your `package.json`:\n  ```json\n  {\n    \"scripts\": {\n      \"truss\": \"truss\"\n    }\n  }\n  ```\n- Copy/paste an initial [truss-config.ts](https://raw.githubusercontent.com/homebound-team/truss/main/packages/template-tachyons/truss-config.ts) into your project\n  - `wget https://raw.githubusercontent.com/homebound-team/truss/main/packages/template-tachyons/truss-config.ts`\n- Run `npm run truss`\n  - Re-run `npm run truss` anytime you change `truss-config.ts`\n- Start using `Css.mt1.etc.$` in your project and wire `trussPlugin(...)` in Vite (see setup below)\n\nWe recommend checking the `src/Css.ts` file into your repository, with the rationale:\n\n- Your design system be pretty stable, so the `Css.ts` output should rarely change.\n- When it does change, it can be nice to see the diff-d output in the PR for others to review.\n- It's the simplest \"just works\" setup for new contributors.\n\n### Vite Plugin Setup (pre-compiled libraries)\n\nIf you're building a component library with Truss that will be consumed by downstream applications, the recommended approach is to compile the library's `Css.*.$` expressions into a pre-built `truss.css` file that the consuming application's Vite plugin can merge with its own Truss-generated CSS, into a single, unified/deduped `truss.css` output file.\n\nWithin the library build, Truss will generate a both `Css.ts` and `Css.json`:\n\n- `Css.ts` is the typed `Css.*.$` DSL to use in your component code,\n- `Css.json` is a metadata file consumed by the Truss Vite plugin at build time.\n\nThe component library then ships _both_ the `Css.ts` DSL and the `Css.json` metadata file for downstream applications to use for a) styling the application's own components, and then b) creating a unified design system + application code `truss.css` file for production usage.\n\nWithin the component library, install the build dependency:\n\n```bash\nnpm install --save-dev @homebound/truss\n```\n\n1. In the **library** package (i.e. your shared, company-wide component library) that defines your Truss styles/design system tokens, run codegen and build with the Truss plugin.\n\n   ```ts\n   // truss-config.ts\n   export default defineConfig({\n     outputPath: \"./src/Css.ts\",\n     // optional: defaults to ./src/Css.json based on outputPath\n     mappingOutputPath: \"./src/Css.json\",\n     // ...any palette/fonts/increment/etc configuration...\n   });\n   ```\n\n   If the library builds with **Vite**, use the Vite plugin:\n\n   ```ts\n   // vite.config.ts (library package)\n   import { defineConfig } from \"vite\";\n   import { trussPlugin } from \"@homebound/truss/plugin\";\n\n   export default defineConfig({\n     plugins: [trussPlugin({ mapping: \"./src/Css.json\" })],\n     build: {\n       lib: {\n         /* your library entry */\n       },\n     },\n   });\n   ```\n\n   If the library builds with **tsup** (or esbuild), use the esbuild plugin:\n\n   ```ts\n   // tsup.config.ts (library package)\n   import { defineConfig } from \"tsup\";\n   import { trussEsbuildPlugin } from \"@homebound/truss/plugin\";\n\n   export default defineConfig({\n     entry: [\"src/index.ts\"],\n     esbuildPlugins: [trussEsbuildPlugin({ mapping: \"./src/Css.json\" })],\n   });\n   ```\n\n   Both plugins transform `Css.*.$` expressions to plain objects and emit a `truss.css` file with annotations that enable correct merging. `.css.ts` arbitrary-selector rules are also emitted into `truss.css` and preserved as opaque blocks during app-level merges.\n\n   For Vitest, use the Vite plugin:\n\n   ```ts\n   // vitest.config.ts (library package)\n   import { defineConfig } from \"vitest/config\";\n   import { trussPlugin } from \"@homebound/truss/plugin\";\n\n   export default defineConfig({\n     plugins: [trussPlugin({ mapping: \"./src/Css.json\" })],\n     test: {\n       environment: \"jsdom\",\n     },\n   });\n   ```\n\n   To assert Truss-generated styles in tests, Truss also exports a `toHaveStyle` matcher:\n\n   ```ts\n   // testSetup.ts\n   import { expect } from \"vitest\";\n   import \"@testing-library/jest-dom/vitest\";\n   import { toHaveStyle } from \"@homebound/truss/vitest\";\n\n   expect.extend({ toHaveStyle });\n   ```\n\n   This gives you both the matcher implementation and Vitest type augmentation. You can then write assertions like:\n\n   ```ts\n   expect(element).toHaveStyle({ display: \"flex\", color: \"#353535\" });\n   ```\n\n2. Publish the library's compiled JS, `Css.json`, and `truss.css` (for example in `dist/`). Then:\n   - Application code can import the design system styles directly, e.g. `import { Css } from \"@company/library\"`.\n   - The application does **not** need to run its own Truss codegen step\n   - In the application's Vite config, point `mapping` to the library's `Css.json` and `libraries` to the library's `truss.css`:\n\n     ```ts\n     import { defineConfig } from \"vite\";\n     import react from \"@vitejs/plugin-react\";\n     import { trussPlugin } from \"@homebound/truss/plugin\";\n\n     export default defineConfig({\n       plugins: [\n         trussPlugin({\n           // If you don't have a design library, just pass ./src/Css.json\n           mapping: \"./node_modules/@company/library/dist/Css.json\",\n           // Pre-compiled CSS from libraries to merge with app CSS\n           libraries: [\"./node_modules/@company/library/dist/truss.css\"],\n         }),\n         react(),\n       ],\n     });\n     ```\n\nNotes:\n\n- Keep `trussPlugin(...)` before `react()`.\n- `mapping` is required and should point to the single `Css.json` you want to compile against.\n- `libraries` lists paths to pre-compiled `truss.css` files that will be merged with the app's own generated CSS. Rules are deduplicated by class name and sorted by priority to produce a correct unified stylesheet.\n\n### Plugin Comparison\n\nTruss ships two build plugins. Both transform `Css.*.$` expressions into plain objects and emit a `truss.css` file, but they target different build tools and have different feature sets.\n\n| Feature                   | Vite plugin (`trussPlugin`)                                                       | esbuild plugin (`trussEsbuildPlugin`)                         |\n| ------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------- |\n| **Build tool**            | Vite                                                                              | esbuild / tsup                                                |\n| **Use case**              | Applications and Vitest test suites                                               | Library packages compiled with tsup or plain esbuild          |\n| **Dev server HMR**        | Yes -- serves CSS via a virtual endpoint and pushes updates over WebSocket        | No -- esbuild has no dev server                               |\n| **Content-hashed output** | Yes -- production builds emit `assets/truss-\u003chash\u003e.css` for long-term caching     | No -- writes a fixed `truss.css` to the output directory      |\n| **Library CSS merging**   | Yes -- `libraries` option merges pre-compiled library CSS into the app stylesheet | No -- libraries are merged by the consuming app's Vite plugin |\n| **Test CSS injection**    | Yes -- auto-injects CSS into jsdom for Vitest                                     | No                                                            |\n| **HTML injection**        | Yes -- injects `\u003clink\u003e` / `\u003cscript\u003e` tags into `index.html`                       | No -- not applicable for library builds                       |\n\n**When to use which:**\n\n- **`trussPlugin`** -- Use for any Vite-based application or when running tests with Vitest. This is the primary plugin for most projects.\n- **`trussEsbuildPlugin`** -- Use when building a shared component library with tsup or esbuild. The library's emitted `truss.css` (with priority annotations) is then consumed by the application's Vite plugin via the `libraries` option.\n\n### React Native (experimental/mobile) Usage\n\nIf you are targeting React Native instead, set `target: \"react-native\"` in your `truss-config.ts` (and typically `defaultMethods: \"tachyons-rn\"`).\n\n## Pseudo-Selectors and Media Queries\n\nTruss intentionally limits the selectors you can use in `Css.*.$` chains to:\n\n- 1. Keep atomic class output deterministic,\n- 2. Discourage selectors that \"reach into other components\" to manipulate their styles\n\nSuch that, in canonical Truss usage, you can only use selectors that _directly modify the element you're styling_, i.e.:\n\n- `Css.onHover` -\u003e when I'm hovered, modify my styles\n- `Css.when(marker, \"ancestor\", \":hover\")` -\u003e when my ancestor is hovered, modify my styles\n\nUnlike Tachyons and Tailwinds, Truss does not create duplicate/repetitive abbreviations for every pseudo-selector and breakpoint variant (e.g. `md-blue` or `lg-red`).\n\nInstead, Truss provides chain commands like `onHover`, `ifSm`, and `ifMd` that then \"modify\" the commands that come after them:\n\n```tsx\nfunction MyReactComponent(props: {}) {\n  // Default is mx2/black.\n  // ...unless hovered, then blue\n  // ...unless hovered \u0026 small screen, then blue \u0026 mx1\n  return \u003cdiv css={Css.mx2.black.onHover.blue.ifSm.mx1.$}\u003e...\u003c/div\u003e;\n}\n```\n\nWhere `sm` resolves from the breakpoints you define in `truss-config.ts`.\n\nThe available pseudo-class modifiers are:\n\n| Modifier         | CSS Pseudo-Class |\n| ---------------- | ---------------- |\n| `onHover`        | `:hover`         |\n| `onFocus`        | `:focus`         |\n| `onFocusVisible` | `:focus-visible` |\n| `onFocusWithin`  | `:focus-within`  |\n| `onActive`       | `:active`        |\n| `onDisabled`     | `:disabled`      |\n| `ifFirstOfType`  | `:first-of-type` |\n| `ifLastOfType`   | `:last-of-type`  |\n\nFor arbitrary pseudo-selectors not covered above, use `when`:\n\n```tsx\n// Simple pseudo-selector\n\u003cdiv css={Css.when(\":hover:not(:disabled)\").black.$} /\u003e;\n\n// Marker-based relationship (react to an ancestor's hover)\nconst row = Css.newMarker();\n\u003ctr css={Css.markerOf(row).$}\u003e\n  \u003ctd css={Css.when(row, \"ancestor\", \":hover\").blue.$}\u003e...\u003c/td\u003e\n\u003c/tr\u003e;\n```\n\n### Chaining Modifiers\n\nTruss reads `Css...$` chains left-to-right.\n\nConditions accumulate by \"axis\", and only the latest modifier on the same axis replaces the previous one.\n\nThe available axes are:\n\n| Modifier | Description                         |\n| -------- |-------------------------------------|\n| `if(cond)` / `else` | Starts a runtime boolean branch     |\n| `ifSm`, `ifMd`, `if(\"@media ...\")` | Sets the active media-query         |\n| `onHover`, `onFocus`, `when(\":hover\")` | Sets the \"this element\" selector    |\n| `when(marker, \"ancestor\", \":hover\")`   | Sets the \"related element\" selector |\n| `element(\"::placeholder\")`             | Sets the pseudo-element             |\n\nExamples:\n\n```tsx\nCss.ifSm.onHover.blue.$;\n// small screens \u0026\u0026 hover =\u003e blue\n\nCss.ifSm.if(selected).blue.$;\n// small screens \u0026\u0026 hovered =\u003e blue\n\nCss.ifSm.black.else.white.$;\n// small screens =\u003e black, others =\u003e white\n\nCss.ifSm.when(row, \"ancestor\", \":hover\").blue.$;\n// small screens \u0026\u0026 ancestor hovered =\u003e blue\n\nCss.when(row, \"ancestor\", \":hover\").onFocus.blue.$;\n// ancestor hovered \u0026\u0026 element focused =\u003e blue\n\nCss.onHover.onFocus.blue.$;\n// last same-element pseudo wins (:focus)\n```\n\n## Arbitrary _Build-time_ Selectors\n\nFor selectors not supported by `Css.*.$`, i.e. descendant selectors, `:nth-child(...)`, or library-driven markup hooks, but where the selectors themselves are still:\n\n1. Globally applicable, and\n2. Statically known at build-time\n\nYou can put the selectors in a `.css.ts` file and then attach the exported class name through `Css.className(...)`.\n\n```ts\n// DataGrid.css.ts\nimport { Css } from \"./Css\";\n\nexport const zebraRows = \"zebraRows\";\n\n// These styles will be appended to the `truss.css` output\nexport const css = {\n  // Write whatever selectors you want, using `zebraRows`\n  [`.${zebraRows} tbody tr:nth-child(even) td`]: Css.bgLightGray.$,\n  [`.${zebraRows} tbody tr:hover td`]: Css.bgBlue.white.$,\n  // Or do global selectors if you want, but be careful with specificity and conflicts\n  body: Css.bgWhite.$,\n};\n```\n\n```tsx\n// DataGrid.tsx\nimport { Css } from \"./Css\";\nimport { zebraRows } from \"./DataGrid.css.ts\";\n\nexport function DataGrid() {\n  return (\n    \u003ctable css={Css.w100.className(zebraRows).$}\u003e\n      \u003ctbody\u003e{/* rows */}\u003c/tbody\u003e\n    \u003c/table\u003e\n  );\n}\n```\n\nThis keeps the base element styling in Truss, i.e. `Css.w100`, while using the `.css.ts` class as the anchor for arbitrary selectors.\n\nAt build time, Truss merges both into the final `className` prop.\n\nIf you need arbitrary CSS that the `Css.*.$` DSL does not support (e.g. `!important`, custom properties, or vendor-specific values), you can use a raw string literal or the `Css.raw` tagged template as a property value:\n\n```ts\nexport const css = {\n  body: Css.raw`\n    margin: 16px;\n    background-color: rgba(255, 255, 255, 1);\n    color: rgba(53, 53, 53, 1);\n    font-size: 14px !important;\n    line-height: 20px !important;\n  `,\n};\n```\n\nThe `Css.raw` tag is a pass-through (it returns the string as-is at runtime) but signals to IDEs that the template content is CSS, enabling syntax highlighting and autocomplete (similar to styled-components markup).\n\nRaw strings are emitted as-is, so property names must use standard CSS kebab-case (e.g. `font-size`, not `fontSize`).\n\nYou can also use a plain string literal or untagged template literal:\n\n```ts\nexport const css = {\n  body: `\n    margin: 16px;\n    font-size: 14px !important;\n  `,\n};\n```\n\n**Limitations:**\n\n- Only static and literal-argument chains are supported (e.g. `Css.df.$`, `Css.mt(2).$`, `Css.mtPx(12).$`)\n- Runtime/variable arguments (`Css.mt(x).$`), conditionals (`Css.if(cond).df.$`), pseudo-class modifiers (`Css.onHover.blue.$`), and media query modifiers (`Css.ifSm.blue.$`) are not supported\n\n## Arbitrary _Runtime_ Selectors\n\nWhen selector rules are either:\n\n- 1. Fundamentally driven by runtime values that change the selector itself, or\n- 2. Should be only transiently injecteda/applied while a component is mounted\n\nTruss also provides a `RuntimeStyle` component:\n\n```tsx\nimport { Css, RuntimeStyle } from \"./Css\";\n\nfunction Preview(props: { accent: string }) {\n  return (\n    \u003c\u003e\n      \u003cRuntimeStyle\n        css={{\n          \".preview [data-selected='true']\": Css.bc(props.accent).bgWhite.$,\n        }}\n      /\u003e\n      \u003cdiv className=\"preview\"\u003e...\u003c/div\u003e\n    \u003c/\u003e\n  );\n}\n```\n\n`RuntimeStyle` evaluates its `Css` expressions at runtime, injects a `\u003cstyle\u003e` tag into the DOM, and removes that tag when the component unmounts. Use it for ephemeral selectors or selector rules that depend on runtime values; use `.css.ts` when the rule is static/global and should be baked into the build output.\n\nThe same behavior is available as the `useRuntimeStyle` hook for cases where you prefer a hook over a component:\n\n```tsx\nimport { Css, useRuntimeStyle } from \"./Css\";\n\nfunction Preview(props: { bottomMargin: number }) {\n  useRuntimeStyle({ body: Css.mbPx(props.bottomMargin).$ });\n  return \u003cdiv\u003e...\u003c/div\u003e;\n}\n```\n\n## Truss Command\n\nThe truss command accepts an optional second argument which is the path to your\nconfiguration file. If omitted, it will look for `./truss-config.ts`.\n\n```json\n{\n  \"scripts\": {\n    \"truss\": \"truss path/to/configuration/file.ts\"\n  }\n}\n```\n\n## Configuration\n\nTruss's configuration is done via a `truss-config.ts` file installed into your local project.\n\nSee the comments in [that file](https://raw.githubusercontent.com/homebound-team/truss/main/packages/template-tachyons/truss-config.ts) for the available config options. For example setting up your custom font abbreviations is set via a `FontConfig` hash:\n\n```\n// Defines the typeface abbreviations, the keys can be whatever you want\nconst fonts: FontConfig = {\n  f10: \"10px\",\n  f12: \"12px\",\n  f14: \"14px\",\n  f24: \"24px\",\n  // Besides the \"24px\" shorthand, you can define weight+size+lineHeight tuples\n  tiny: { fontWeight: 400, fontSize: \"10px\", lineHeight: \"14px\" },\n};\n```\n\nAlso see the [Customization](#customization) section for more advanced configuration options.\n\n## Pseudo-Selectors and Media Queries\n\nUnlike Tachyons and Tailwinds, Truss does not create duplicate/repetitive abbreviations for every pseudo-selector and breakpoint variant (e.g. `md-blue` or `lg-red`).\n\nInstead, Truss provides chain commands like `onHover`, `ifSm`, and `ifMd` that then \"modify\" the commands that come after them:\n\n```tsx\nfunction MyReactComponent(props: {}) {\n  return \u003cdiv css={Css.mx2.black.onHover.blue.ifSm.mx1.$}\u003e...\u003c/div\u003e;\n}\n```\n\nWhere `sm` resolves from the breakpoints you define in `truss-config.ts`.\n\nThe available pseudo-class modifiers are:\n\n| Modifier         | CSS Pseudo-Class |\n| ---------------- | ---------------- |\n| `onHover`        | `:hover`         |\n| `onFocus`        | `:focus`         |\n| `onFocusVisible` | `:focus-visible` |\n| `onFocusWithin`  | `:focus-within`  |\n| `onActive`       | `:active`        |\n| `onDisabled`     | `:disabled`      |\n| `ifFirstOfType`  | `:first-of-type` |\n| `ifLastOfType`   | `:last-of-type`  |\n\nFor arbitrary pseudo-selectors not covered above, use `when`:\n\n```tsx\n// Simple pseudo-selector\n\u003cdiv css={Css.when(\":hover:not(:disabled)\").black.$} /\u003e;\n\n// Marker-based relationship (react to an ancestor's hover)\nconst row = Css.newMarker();\n\u003ctr css={Css.markerOf(row).$}\u003e\n  \u003ctd css={Css.when(row, \"ancestor\", \":hover\").blue.$}\u003e...\u003c/td\u003e\n\u003c/tr\u003e;\n```\n\nSee [Chaining Modifiers](#chaining-modifiers) for how boolean `if(...)`, breakpoint `ifSm`, and `when(...)` stack and reset.\n\n## Custom Selectors with `.css.ts` and `Css.className(...)`\n\nFor selectors that do not fit naturally into a `Css.*.$` chain, i.e. descendant selectors, `:nth-child(...)`, or library-driven markup hooks, put that selector logic in a `.css.ts` file and then attach the exported class name through `Css.className(...)`.\n\n```ts\n// DataGrid.css.ts\nimport { Css } from \"./Css\";\n\nexport const zebraRows = \"zebraRows\";\n\nexport const css = {\n  [`.${zebraRows} tbody tr:nth-child(even) td`]: Css.bgLightGray.$,\n  [`.${zebraRows} tbody tr:hover td`]: Css.bgBlue.white.$,\n};\n```\n\n```tsx\n// DataGrid.tsx\nimport { Css } from \"./Css\";\nimport { zebraRows } from \"./DataGrid.css.ts\";\n\nexport function DataGrid() {\n  return (\n    \u003ctable css={Css.w100.className(zebraRows).$}\u003e\n      \u003ctbody\u003e{/* rows */}\u003c/tbody\u003e\n    \u003c/table\u003e\n  );\n}\n```\n\nThis keeps the base element styling in Truss, i.e. `Css.w100`, while using the `.css.ts` class as the anchor for arbitrary selectors. At build time, Truss merges both into the final `className` prop.\n\n## XStyles / Xss Extension Contracts\n\nTruss liberally borrows the idea of type-checked \"extension\" CSS from the currently-unreleased Facebook XStyles library (at least in theory; I've only seen one or two slides for this feature of XStyles, but I'm pretty sure Truss is faithful re-implementation of it).\n\nAs context, when developing components, you often end up with \"properties that are okay for the caller to set\" (i.e. that you as the component developer support the caller setting) and \"properties that are _not_ okay for the caller to set\" (i.e. because the component controls them).\n\nBasically, you want to allow the caller to customize _some_ styles of the component, typically things like color or margin or font size, but not give them blanket control of \"here is a `className` prop, do whatever you want to my root element\", which risks \"radical\"/open-ended customization that then you, as the component developer, don't know if you will/will not unintentionally break going forward.\n\n(I.e. see [Layout isolated components](https://visly.app/blog/layout-isolated-components) for a great write up of \"parents control margin, components control padding\".)\n\nWith Truss, you can explicitly declare a contract of styles allowed to be set on your component, i.e.:\n\n```tsx\nimport { Css, Only, Xss } from \"src/Css\";\n\n// Declare the allowed/supported styles\nexport type DatePickerXss = Xss\u003c\"marginLeft\" | \"marginRight\"\u003e;\n\n// Update the props to accept an `xss` prop to accept the customizations\nexport interface DatePickerProps\u003cX\u003e {\n  date: Date;\n  xss?: X;\n}\n\n// Use the `Only` type to ensure `xss` prop is a subset of DatePickerXss\nexport function DatePicker\u003cX extends Only\u003cDatePickerXss, X\u003e\u003e(props: DatePickerProps\u003cX\u003e) {\n  const { date, xss } = props;\n  // The component controls marginTop/marginBottom, and defers to the caller for marginLeft/marginRight\n  return \u003cdiv css={{ ...Css.my2.$, ...xss }}\u003e{date}\u003c/div\u003e;\n}\n```\n\nHere we're allowing callers to set `marginLeft` or `marginRight`, i.e. this line will compile because `mx2` is statically typed as `{ marginLeft: number; marginRight: number }`, and so is a valid `xss` value:\n\n```tsx\n\u003cDatePicker xss={Css.mx2.$} date={...} /\u003e\n```\n\nHowever, this line will not compile because `mt2` is statically typed as `{ marginTop: number }`, and `marginTop` is not allowed by `DatePickerXss`:\n\n```tsx\n\u003cDatePicker date={...} xss={Css.mt2.$} /\u003e\n```\n\nThe `Css` DSL also iteratively types itself, i.e. `Css.ml1.mr2.$` is still statically typed as `{ marginLeft: number; marginRight: number }`, instead of being based just on the last-used abbreviation.\n\nYou can also destructure an `xss` value for component logic, and then re-apply specific overrides with `addCss(...)`. A useful pattern is to put the component's fallback/default earlier in the chain, and let the caller's destructured override win later:\n\n```tsx\nimport { Css, type Only, type Xss } from \"src/Css\";\n\ntype PanelXss = Xss\u003c\"color\" | \"height\"\u003e;\n\nfunction Panel\u003cX extends Only\u003cPanelXss, X\u003e\u003e(props: { xss?: X }) {\n  const xss = props.xss as Partial\u003cPanelXss\u003e | undefined;\n  const { height } = xss ?? {};\n\n  return \u003cdiv css={Css.h(1).black.addCss({ height }).$}\u003epanel\u003c/div\u003e;\n}\n```\n\nIn this example, `Css.h(1)` provides the default height, and `addCss({ height })` only overrides it when the caller actually passed a `height` xss value.\n\nThis is very similar to doing a spread on `...{ height }` but note that, if the spread height is `undefined`, this will drop any previous `height` values--the `addCss` method will noticed the `undefined` and skip it.\n\nTruss conventionally uses the `xss` prop name for \"the component's allowed extension styles\" as a play on the `css` prop name, with the `x` representing the \"extension\" concept, but otherwise there is nothing special about the name of the `xss` prop.\n\nAlso note that the XStyles/Xss feature is completely opt-in; you can use it if you want, or you can use Truss solely for the `Css.m2.black.$` abbreviations.\n\n## Customization\n\nTruss supports several levels of customization:\n\n1. Per-project fonts/colors/etc. in `truss-config.ts`\n2. Per-project rule additions or changes in `truss-config.ts`\n3. Forking\n\n### Per-Project Fonts/Colors/Etc\n\nEach project that uses Truss gets a local `index.ts`, checked into its repo essentially as a config file, that defines in TypeScript the core settings, i.e.:\n\n```typescript\nconst increment = 8;\nconst numberOfIncrements = 4;\n\nconst palette = {\n  Black: \"#353535\",\n  MidGray: \"#888888\",\n  LightGray: \"#cecece\",\n  White: \"#fcfcfa\",\n  Blue: \"#526675\",\n};\n\nconst fonts = {\n  f24: \"24px\",\n  f18: \"18px\",\n  f16: \"16px\",\n  f14: \"14px\",\n  f12: \"12px\",\n  // Can also set multiple properties if necessary\n  f10: { fontSize: \"10px\", fontWeight: 500 },\n};\n\nconst breakpoints = { sm: 0, md: 600, lg: 960 };\n\n// ...rest of the config file...\n```\n\nProjects should heavily customize these settings to match their project-specific design system, then run `npm run truss` to get an updated `Css.ts`, i.e. after adding `Green: \"green\"` as a color in `palette`, the `Css.ts` file will automatically have utility methods added like:\n\n```typescript\n  get green() { return this.add(\"color\", \"green\"); }\n  get bgGreen() { return this.add(\"backgroundColor\", \"green\"); }\n  get bGreen() { return this.add(\"borderColor\", \"green\"); }\n\n```\n\n### Per-Project Utility Methods\n\nIn the same `index.ts`, projects can add their own new abbreviations/utility methods:\n\n```typescript\nconst sections = {\n  ourSection: () =\u003e [newMethod(\"someAbbreviation\", { color: \"#000000\" })],\n};\n```\n\nWill result in `Css.ts` having a line that looks like:\n\n```typescript\n  // ourSection\n  get someAbbreviation() { return this.add(\"color\", \"#000000\"); }\n```\n\nWhich can then be used as `Css.m2.someAbbreviation.$`.\n\nBesides adding one-off additional methods, if your project wants to replace a whole section of Truss's out-of-the-box methods, you can do this via:\n\n```typescript\nconst sections = {\n  // Prefer app-specific border radiuses\n  borderRadius: () =\u003e\n    newMethodsForProp(\"borderRadius\", {\n      br4: \"4px\",\n      br8: \"8px\",\n      br16: \"16px\",\n    }),\n};\n```\n\nWhere `borderRadius` matches the name of the section in Truss's [sections](https://github.com/homebound-team/truss/tree/main/src/sections) directory (which generally matches Tachyon's organization).\n\n### Forking\n\nAt the end of the day, Truss is small and hackable such that forking it to make the abbreviations \"strict Tachyons\" or \"strict Tailwinds\" or \"whatever best fits your project/conventions/styles\" should be easy and is kosher/encouraged.\n\nThe core Truss feature of \"make a TypeScript DSL with a bunch of abbreviations\" is also basically done, so it's unlikely you will miss out on some future/forthcoming amazing features by forking.\n\nAnd, even if so, the coupling between Truss and your application code is limited to the `Css.abbreviations.$` lines that should be extremely stable even if/as the core of Truss evolves.\n\n## Why This Approach?\n\nTruss's approach is \"Tachyons-ish\" (or Tailwinds-ish), insofar as having short/cute utility class definitions.\n\nOn web, those abbreviations compile through the Truss Vite plugin into atomic CSS classes. On mobile, they resolve to plain React Native style objects.\n\nThe benefits of this approach are:\n\n- We get the brevity + \"inline-ness\" of Tachyons/Tailwinds.\n\n- It delivers critical CSS, i.e. we don't need the large static TW/Tachyons CSS files.\n\n  (My reading of projects like [tachyons-styled-react](https://github.com/tachyons-css/tachyons-styled-react), from the creator of Tachyons, is that critical-ness is still important goal/improvement even for static-utility-class approaches like Tachyons.)\n\n- Pseudo-selectors/breakpoints go through Truss's typed DSL (`onHover`, `ifSm`, etc.), which keeps usage concise and reduces method/abbreviation bloat.\n\n  I.e. we don't need to suffix `-nl` for \"not large\" onto every single abbreviation.\n\n- You can still mix in regular CSS for the places where utility abbreviations are not the best fit (see `.css.ts` files).\n\n- Projects can easily tweak their preferred styles/abbreviations.\n\n  Granted, this is very similar in spirit to Tailwinds customization, but for Truss, the config process is \"just change some TypeScript code and run `generate`\", and doesn't involve any changes to your build/webpack/PostCSS/etc. setup.\n\n## Why Tachyons Instead of Tailwinds?\n\ntldr: Tachyon's abbreviations are shorter. :-)\n\nFor example, the CSS `justify-content: flex-start` in Tailwinds is `justify-start`, and in Truss is `jcfs` (i.e. the Justify Content Flex Start).\n\nThis is admittedly preference, but Truss's assertion is that **readability goes up when code sprawl goes down**, because you can visually fit more code into view at once.\n\nAnd so Tachyons-style abbreviations, even if each abbreviation in isolation is more complex, when taken as a whole (looking at 10-20 lines of non-trivially-styled JSX) is arguably more readable.\n\nGranted, the more-succinct code is still doing \"the same work\" (setting the same CSS properties) as the longer code, but you are likely only paying attention to a small subset of code at any given time, so the currently-unimportant code/abbreviations will \"fade into the background\" more easily when they're shorter.\n\n(This is also not to say all names in a codebase should be meaningless chicken-scratch like `a`, `b`, `c`, etc., but when there are very strong/consistent conventions (like loop variables being called `i`, `j`, `k`), then leaning into succinctness can be appropriate.)\n\nAll this said, it's very possible to teach Truss how to generate Tailwinds-based abbreviations, we just haven't done it yet; see [this issue](https://github.com/homebound-team/truss/issues/65) if you're interested in helping contribute.\n\n## Themes\n\nThe word \"theme\" can mean either \"static themes\" (i.e. using the same consistent colors/fonts throughout your app, but the values themselves never really change) or \"dynamic themes\" (i.e. the user changing from light mode to dark mode).\n\nFor static themes, Truss's `index.ts`/`palette.ts` are specifically setup to centrally define your application's fonts, colors, etc. (see the \"Configuration\" section), so that they are consistently applied through your application.\n\nFor dynamic themes, Truss doesn't have any features dedicated explicitly to support them, but you can easily use CSS variables in your methods, i.e.:\n\n```typescript\nconst palette = {\n  Primary: \"var(--primary)\",\n  Secondary: \"var(--secondary)\",\n};\n```\n\nAnd then have your application handle setting the `--primary` / `--secondary` values as appropriate (i.e. by importing a `dark-mode.css` or `light-mode.css` which define the respective CSS variable values).\n\n## Inspiration\n\nSeveral libraries influenced Truss, specifically:\n\n- [Typed Tailwinds](https://typed.tw) and [babel-plugin-tailwind-components](https://github.com/bradlc/babel-plugin-tailwind-components) are both \"type-safe TypeScript utility-css DSLs\".\n\n  In particular, the babel-plugin-tailwind-components insight of \"if you just make `csstype`-compliant object literals, you can build a typed utility DSL on top\" was a very useful/inspirational insight.\n\n- Facebook's [XStyles](https://www.youtube.com/watch?v=9JZHodNR184) for the \"typed extension\" idea\n\n- Facebook's [StyleX](https://stylexjs.com/) heavily influenced Truss's 2.x build-time approach--i.e. we copied nearly everything about it. 😅\n\n  StyleX solved the hard problems of build-time atomic CSS:\n  - property-level last-write-wins semantics,\n  - specificity tiers via doubled selectors for media queries,\n  - CSS custom properties for runtime values, and\n  - deterministic class generation.\n\n  The only reasons we don't use StyleX directly are:\n  - The `stylex.create` values are \"arrays of tuple data\", instead of object hashes, and so didn't work with Truss's extremely common object literal spreads of `css={{ ...Css.mt2.$, ...someOtherStyles }}`.\n\n  - Given we already have \"basically unique\" abbreviations, we can make class names that aren't esoteric hashes.\n\n    We probably give up some small-percentage of output size/performance, that matters at Facebook scale, but for Truss we prioritize readability and debuggability of the emitted CSS classes.\n\n## Contributing\n\nThe Truss repository is set up as a Yarn workspace, although the core package is just `packages/truss`; the other packages are examples/tests projects.\n\nA basic development flow is:\n\n- In the root directory, run `yarn`\n- In the root directory, run `yarn build -w`\n- Iterate as you want\n- In the root directory, run `yarn test` to run all tests\n  - Running individual tests in your IDE/each package should work as well\n- In the root directory run `yarn codegen` to generate the testing `Css.ts` files\n\n## Todo\n\n- `npx -p @homebound/truss init` type experience for setup - inspired by [Storybook](https://storybook.js.org/docs/guides/quick-start-guide/)\n- Support `number[]` increments as config\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhomebound-team%2Ftruss","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhomebound-team%2Ftruss","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhomebound-team%2Ftruss/lists"}