{"id":32016707,"url":"https://github.com/lloydrichards/proj_effect-box-web-components","last_synced_at":"2026-05-01T21:33:38.233Z","repository":{"id":317826482,"uuid":"1068944378","full_name":"lloydrichards/proj_effect-box-web-components","owner":"lloydrichards","description":"Some experimentation in creating web components using lit and effect","archived":false,"fork":false,"pushed_at":"2026-02-18T21:03:49.000Z","size":839,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-19T01:01:48.208Z","etag":null,"topics":["effect-ts","lit","tailwindcss","web-components"],"latest_commit_sha":null,"homepage":"https://proj-effect-box-web-components.vercel.app","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/lloydrichards.png","metadata":{"files":{"readme":"README.md","changelog":null,"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":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-10-03T06:47:13.000Z","updated_at":"2025-11-23T12:52:01.000Z","dependencies_parsed_at":"2025-10-15T19:26:03.739Z","dependency_job_id":null,"html_url":"https://github.com/lloydrichards/proj_effect-box-web-components","commit_stats":null,"previous_names":["lloydrichards/proj_effect-box-web-components"],"tags_count":0,"template":false,"template_full_name":"lloydrichards/base_lit-with-tailwind","purl":"pkg:github/lloydrichards/proj_effect-box-web-components","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lloydrichards%2Fproj_effect-box-web-components","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lloydrichards%2Fproj_effect-box-web-components/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lloydrichards%2Fproj_effect-box-web-components/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lloydrichards%2Fproj_effect-box-web-components/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lloydrichards","download_url":"https://codeload.github.com/lloydrichards/proj_effect-box-web-components/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lloydrichards%2Fproj_effect-box-web-components/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32513714,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-30T13:12:12.517Z","status":"online","status_checked_at":"2026-05-01T02:00:05.856Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["effect-ts","lit","tailwindcss","web-components"],"created_at":"2025-10-16T00:04:47.412Z","updated_at":"2026-05-01T21:33:38.223Z","avatar_url":"https://github.com/lloydrichards.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Effect-Atom Web Components\n\nA deep-dive into the integration of [Effect](https://effect.website/) with\n[Lit](https://lit.dev/) web components using the\n[Effect-Atom](https://github.com/tim-smart/effect-atom) state management\nlibrary. Building a suite of web-components that leverage Effect for building\napplications on the client-side\n\n## Project Structure\n\n```\nlib/\n├── components/          # Web component examples\n│   ├── ui/              # Reusable UI components\n│   ├── atom-counter.ts\n│   ├── atom-stream-counter.ts\n│   ├── scoped-counter.ts\n│   └── atom-secrets.ts\n├── shared/\n│   ├── atomMixin.ts     # Core AtomMixin implementation\n│   ├── tailwindMixin.ts # Tailwind CSS integration\n│   └── utils.ts         # Shared utilities\n└── main.ts              # Entry point\n```\n\n## Getting Started\n\n```bash\nbun install\n\n# Start dev server\nbun run dev\n\n# Build library\nbun run build\n\n# Type check\nbun run type-check\n\n# Lint \u0026 format\nbun run lint\nbun run format\n```\n\n## Quick Start\n\n### 1. Create an Atom\n\nAtoms are reactive containers for state. Use `Atom.fn` to create atoms that\nexecute Effect programs:\n\n```typescript\nimport { Atom, Result } from \"@effect-atom/atom\";\nimport { Effect, Data } from \"effect\";\n\nclass CountError extends Data.TaggedError(\"CountError\")\u003c{ message: string }\u003e {}\n\n// Create a writable atom that runs an Effect\nconst countAtom = Atom.fn(\n  (newValue: number) =\u003e\n    Effect.gen(function* () {\n      if (newValue \u003c 0) {\n        return yield* new CountError({ message: \"Count must be non-negative\" });\n      }\n      yield* Effect.sleep(\"100 millis\");\n      return newValue;\n    }),\n  { initialValue: 0 }\n);\n```\n\n### 2. Use AtomMixin in Your Component\n\nThe `AtomMixin` provides the bridge between Effect-Atom and Lit components:\n\n```typescript\nimport { html, LitElement } from \"lit\";\nimport { customElement } from \"lit/decorators.js\";\nimport { AtomMixin, atomState } from \"./shared/atomMixin\";\n\n@customElement(\"my-counter\")\nexport class MyCounter extends AtomMixin(LitElement) {\n  // Automatically sync atom state to component property\n  @atomState(countAtom) declare count: Result.Result\u003cnumber, CountError\u003e;\n\n  render() {\n    return html`\n      \u003cdiv\u003e\n        \u003cbutton @click=${this._increment}\u003eIncrement\u003c/button\u003e\n        \u003cp\u003eCount: ${Result.isSuccess(this.count) ? this.count.value : 0}\u003c/p\u003e\n      \u003c/div\u003e\n    `;\n  }\n\n  private _increment() {\n    const setCount = this.useAtomSet(countAtom);\n    const current = Result.isSuccess(this.count) ? this.count.value : 0;\n    setCount(current + 1);\n  }\n}\n```\n\n## Core Concepts\n\n### AtomMixin\n\nThe `AtomMixin` is a Lit mixin that adds reactive atom capabilities to your\ncomponents. It provides several methods for working with atoms:\n\n#### `useAtom\u003cR, W\u003e(atom)`\n\nGet both value and setter for a writable atom:\n\n```typescript\nconst [count, setCount] = this.useAtom(countAtom);\nsetCount(5); // Set directly\nsetCount((prev) =\u003e prev + 1); // Update based on previous value\n```\n\n#### `useAtomValue\u003cA\u003e(atom)`\n\nRead-only access to an atom's value:\n\n```typescript\nconst count = this.useAtomValue(countAtom);\n```\n\n#### `useAtomSet\u003cR, W\u003e(atom)`\n\nGet just the setter function without reading the value:\n\n```typescript\nconst setCount = this.useAtomSet(countAtom);\nsetCount(10);\n```\n\n#### `useAtomPromise\u003cA, E\u003e(atom)`\n\nConvert a Result atom into a Promise:\n\n```typescript\nconst data = await this.useAtomPromise(dataAtom);\n```\n\n#### `useAtomRefresh\u003cA\u003e(atom)`\n\nGet a function to manually refresh an atom:\n\n```typescript\nconst refresh = this.useAtomRefresh(dataAtom);\nrefresh(); // Re-evaluate the atom\n```\n\n#### `useAtomMount\u003cA\u003e(atom, options?)`\n\nExplicitly mount an atom with optional reactivity keys:\n\n```typescript\nthis.useAtomMount(dataAtom, { reactivityKeys: [\"user\", \"settings\"] });\n```\n\n#### `invalidate(keys)`\n\nManually refresh atoms associated with specific reactivity keys:\n\n```typescript\nthis.invalidate([\"user\"]); // Refresh all atoms tagged with \"user\"\n```\n\n### @atomState Decorator\n\nThe `@atomState` decorator automatically syncs atom values to component\nproperties and triggers re-renders on changes:\n\n```typescript\n@atomState(myAtom) declare myValue: number;\n@atomState(resultAtom) declare myResult: Result.Result\u003cstring, Error\u003e;\n```\n\nIt uses Lit's `@state()` internally, making the property reactive but private\n(not exposed as an HTML attribute).\n\n### Result Pattern\n\nEffect-Atom's `Result\u003cA, E\u003e` type represents async operations with four states:\n\n- **Initial** - Not yet executed\n- **Waiting** - In progress\n- **Success** - Completed successfully with value `A`\n- **Failure** - Failed with error `E`\n\nUse the `matchResult` helper to handle all states:\n\n```typescript\nimport { matchResult } from \"./shared/atomMixin\";\n\nrender() {\n  return matchResult(this.result, {\n    onInitial: () =\u003e html`\u003cspan\u003eNot started\u003c/span\u003e`,\n    onWaiting: () =\u003e html`\u003cspan\u003eLoading...\u003c/span\u003e`,\n    onSuccess: (value) =\u003e html`\u003cspan\u003eValue: ${value}\u003c/span\u003e`,\n    onFailure: (error) =\u003e html`\u003cspan\u003eError: ${error.message}\u003c/span\u003e`,\n  });\n}\n```\n\n### Global vs Scoped State\n\n**Global Registry (default)** - Share state across all component instances:\n\n```typescript\nconst countAtom = Atom.make(0);\n\n// Both instances share the same count\n@customElement(\"counter-a\")\nclass CounterA extends AtomMixin(LitElement) {}\n\n@customElement(\"counter-b\")\nclass CounterB extends AtomMixin(LitElement) {}\n```\n\n**Scoped Registry** - Isolate state per component instance or component tree:\n\n```typescript\nimport { Registry } from \"@effect-atom/atom\";\n\nconst scopedRegistry = Registry.make({\n  scheduleTask: (f) =\u003e queueMicrotask(f),\n  timeoutResolution: 1000,\n  defaultIdleTTL: 30_000,\n});\n\nconst scopedAtom = Atom.make(0);\n\n// Each instance has its own independent count\n@customElement(\"isolated-counter\")\nclass IsolatedCounter extends AtomMixin(LitElement, scopedRegistry) {\n  @atomState(scopedAtom) declare count: number;\n}\n```\n\n## Learn More\n\n- [Effect Documentation](https://effect.website/docs/introduction)\n- [Effect-Atom](https://github.com/tim-smart/effect-atom)\n- [Lit Documentation](https://lit.dev/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flloydrichards%2Fproj_effect-box-web-components","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flloydrichards%2Fproj_effect-box-web-components","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flloydrichards%2Fproj_effect-box-web-components/lists"}