{"id":26165129,"url":"https://github.com/xiaohk/streamlit-component-example","last_synced_at":"2026-04-22T01:31:35.562Z","repository":{"id":280399037,"uuid":"941859089","full_name":"xiaohk/streamlit-component-example","owner":"xiaohk","description":"Example code to create a streamlit component using web component","archived":false,"fork":false,"pushed_at":"2025-10-21T00:22:46.000Z","size":184,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-26T12:53:04.878Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xiaohk.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":"2025-03-03T07:05:08.000Z","updated_at":"2025-03-03T07:08:10.000Z","dependencies_parsed_at":null,"dependency_job_id":"0d6fcaf6-9da4-4eb6-aa3a-a8d6d9c4deca","html_url":"https://github.com/xiaohk/streamlit-component-example","commit_stats":null,"previous_names":["xiaohk/streamlit-component-example"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/xiaohk/streamlit-component-example","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xiaohk%2Fstreamlit-component-example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xiaohk%2Fstreamlit-component-example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xiaohk%2Fstreamlit-component-example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xiaohk%2Fstreamlit-component-example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xiaohk","download_url":"https://codeload.github.com/xiaohk/streamlit-component-example/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xiaohk%2Fstreamlit-component-example/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32117340,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-22T00:31:26.853Z","status":"ssl_error","status_checked_at":"2026-04-22T00:30:22.894Z","response_time":128,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: 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":[],"created_at":"2025-03-11T15:58:50.581Z","updated_at":"2026-04-22T01:31:35.555Z","avatar_url":"https://github.com/xiaohk.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# How to Create Streamlit Custom Component?\n\n[original article](https://notes.zijie.wang/streamlit-custom-component.html)\n\nStreamlit's support for custom components is limited, and creating one using\nstandard web technologies can be complex. The trickiest aspect is managing the\nevent handling within Streamlit. However, here is a practical solution for\ndeveloping a **Streamlit custom component** with **Web Components** that allows\nfor **two-way communication** with the Streamlit app. You can find all the code\nfor this working example on\n[GitHub](https://github.com/xiaohk/streamlit-component-example).\n\n# 1. Compile Web Component\n\n## 1.1. Component Overview\n\nIn this tutorial, we will walk through an example using Lit Element Web\nComponent. The code of the component quest-board is in the directory\n`quest-board`.\n\nThe component is simple. It takes a string array `quests` as an attribute /\nproperty. It renders the quests. Each quest has a button where user can click to\nquery the reward of the quest. When the button is clicked, the component will\nemit an event with a promise `resolve`. The component consumer is expected to\nresolve the promise with the reward associated with the quest.\n\nBelow is an example how we use this web component in HTML (`index.html`). It\npasses three quests to the component. When the component asks for a quest\nreward, it returns a random number as the reward.\n\n```html\n\u003cbody\u003e\n  \u003cquest-board\n    quests='[\"Echoes of the Forgotten Realm\", \"The Ashen King\u0026apos;s Bargain\", \"The Clockwork Gambit\"]'\n  \u003e\u003c/quest-board\u003e\n\u003c/body\u003e\n\n\u003cscript\u003e\n  const questBoard = document.querySelector('quest-board');\n  questBoard.addEventListener('quest-clicked', (event) =\u003e {\n    const { resolve, reject } = event.detail;\n    const randomReward = Math.floor(Math.random() * 10000);\n    resolve(randomReward);\n  });\n\u003c/script\u003e\n```\n\nWe can try out this component by:\n\n```bash\ncd quest-board\npnpm install\npnpm run dev\n```\n\n\u003cimg align=\"center\" width=\"350px\" src=\"https://github.com/user-attachments/assets/e6a6342f-61fd-444a-82d3-d2547fa6bb63\"\u003e\n\nExample interaction of the quest-board component.\n\n## 1.2. Compile the Component to a JS File\n\nWe use Vite’s library mode to build the web component as a JS file. Consumers\ncan import the JS file and use our component by writing HTML element\n`\u003cquest-board\u003e\u003c/quest-board\u003e`.\n\n```bash\ncd quest-board\npnpm install\npnpm run build:library\n```\n\n```json\n# package.json: define the library entry point\n\"exports\": {\n  \".\": {\n    \"import\": {\n      \"types\": \"./lib/quest-board.d.ts\",\n      \"default\": \"./lib/index.js\"\n    }\n  }\n},\n```\n\n# 2. Integrate Web Component into Streamlit\n\nDownload the official Streamlit custom component template from\n[GitHub](https://github.com/streamlit/component-template). We will use the\nreact-less template.\n\n## 2.1. Import Compiled Web Component\n\nWe add an NPM dependency of our web component from the template’s frontend NPM\npackage.\n\n```bash\ncd quest_board_stremlit/frontend\npnpm add ../../../quest_board\npnpm install\n```\n\nThen, in `frontend/index.tsx` , we will add code to create the `\u003cquest-board\u003e`\nelement and bind data properties as well as event handlers.\n\n```tsx\nimport 'quest-board';\n\nthis.questBoardContainer = document.body.appendChild(\n  document.createElement('div')\n);\nthis.questBoardContainer.classList.add('quest-board-container');\nthis.questBoardElement = this.questBoardContainer.appendChild(\n  document.createElement('quest-board')\n);\n```\n\n## 2.2. Streamlit Component Development\n\nNext, we willl create a Streamlit Component by building a wrapper that manages\nthe data and events for our `quest-board` component. To view the Streamlit\nComponent, we need to launch two servers:\n\n**Frontend**\n\n```bash\ncd quest_board_stremlit/quest_board_stremlit/frontend\npnpm install\npnpm run start\n```\n\n**Backend**\n\n```bash\ncd quest_board_streamlit\npip install -e .\nstreamlit run quest_board_stremlit/example.py\n```\n\n# 3. Design Data and Event Flow\n\nThe most complex aspect is managing the data and event flow. Unlike a\ntraditional browser's event-driven flow, Streamlit uses a declarative approach,\nmeaning changes only appear in the app after calling `st.rerun()`. We'll divide\nthe data and event flow into two sections: (1) between the **web component** and\nthe Streamlit **wrapper frontend**, and (2) between the Streamlit **wrapper\nfrontend** and the **backend**.\n\n\u003cimg align=\"center\" width=\"100%\" src=\"https://github.com/user-attachments/assets/b5133265-28a1-4e6d-9d24-dbb4e9e362ec\"\u003e\n\n## 3.1. Web Component ↔ Wrapper Frontend\n\n### **Data**\n\nThe wrapper frontend will pass the data to our web component through the typical\nHTML attributes or JavaScript component properties. This is implemented in\n`frontend/index.tsx`.\n\n```tsx\nthis.questBoardElement.quests = data.args.quests;\n```\n\n### Event\n\nThe event flow is also similar to typical web apps. We declare event listeners\nto catch events coming from `\u003cquest-board\u003e` .\n\n```tsx\nthis.questBoardElement.addEventListener('quest-clicked', (event) =\u003e {});\n```\n\n## 3.2. Wrapper Frontend ↔ Wrapper Backend\n\n### Data\n\nThe data flows between Streamlit Python backend and JavaScript frontend through\nprops. We define the props in `__init__.py` , and the JavaScript can access\npassed props through the special `Streamlit.RENDER_EVNT` event.\n\n```python\n# __init__.py\ndef quest_board(\n    quests: list[str], event_responses: list[QuestBoardResponse], key: str | None = None\n) -\u003e dict[str, list[QuestBoardEvent]]:\n```\n\n```tsx\n// frontend/index.tsx\nStreamlit.events.addEventListener(Streamlit.RENDER_EVENT, (event: Event) =\u003e {\n  questBoardComponent.onRender(event);\n});\n\nonRender(event: Event): void {\n  // Get the RenderData from the event\n  const data = (event as CustomEvent\u003cRenderData\u003cEuphonyComponentProps\u003e\u003e)\n    .detail;\n  const questsString = JSON.stringify(data.args.quests);\n}\n```\n\n### Event\n\nIn Streamlit's wrapper backend, we can't create event listeners directly.\nInstead, we use the existing data communication channel to manage events. In\nStreamlit, a component can send a reactive value back to the backend, which we\ncan use to include **event requests**. Once the backend processes these\nrequests, we send the **event responses** back to the frontend using the same\nprops.\n\n```python\n# example.py\n\n# Create the component and its reactive component_value\ncomponent_value = quest_board(quests, st.session_state[\"event_responses\"], key=key)\n\n# \"Event loop\": continuously checking if there are new requests in component_value\n# If there are new requests, handle them, and use st.rerun() to send the response\n# back into the frontend\nif component_value and component_value[\"events\"]:\n    # Clean the previous responses\n    st.session_state[\"event_responses\"] = []\n    events_to_handle = []\n\n    for e in component_value[\"events\"]:\n        if e[\"uniqueID\"] not in st.session_state[\"handled_events\"]:\n            events_to_handle.append(e)\n\n    # Handle the events\n    tasks = []\n    for e in events_to_handle:\n        tasks.append(handle_event(e))\n\n    await asyncio.gather(*tasks)\n\n    if len(events_to_handle) \u003e 0:\n        st.rerun(scope=\"fragment\")\n```\n\n```tsx\n// frontend/index.tsx\n\n// Helper function to send event requests to streamlit component value\n_dispatchPendingEvent(\n  eventName: string,\n  detail: string,\n  uniqueID: string,\n  resolve: (value: any) =\u003e void,\n  reject: (reason?: any) =\u003e void\n) {\n  this.pendingEvents.push({\n    event: eventName,\n    detail: detail,\n    uniqueID\n  });\n\n  this.eventResolveMap.set(uniqueID, {\n    resolve: resolve,\n    reject: reject\n  });\n\n  if (this.eventDispatchTimer) {\n    clearTimeout(this.eventDispatchTimer);\n  }\n\n\t// There is a delay of Streamlit communication.\n\t// We stagger and batch events to prevent some events being missed\n  this.eventDispatchTimer = window.setTimeout(() =\u003e {\n    Streamlit.setComponentValue({\n      events: this.pendingEvents\n    });\n  }, 300);\n}\n\n// Delegate the event handling of quest-clicked to Python\nthis.questBoardElement.addEventListener('quest-clicked', event =\u003e {\n  const customEvent = event as CustomEvent\u003cQuestClickedRequest\u003e;\n  const request = customEvent.detail;\n  const uniqueID = crypto.randomUUID();\n  this._dispatchPendingEvent(\n    'quest-clicked',\n    request.quest,\n    uniqueID,\n    request.resolve,\n    request.reject\n  );\n});\n```\n\nAfter binding the data and events, we now have a working Streamlit component\nwith two-way communication to our custom web component!\n\n\u003cimg align=\"center\" width=\"500px\" src=\"https://github.com/user-attachments/assets/42c71fcc-1cb2-40c0-80da-64c4c00c061c\"\u003e\n\nWorking example of our quest board web component as a streamlit custom\ncomponent.\n\n# 4. Use Streamlit Component in Streamlit App\n\nFinally, we can distribute the component and use it in any Streamlit apps. To\nbuild a wheel and distribute the wheel, check out the\n[official guide](https://docs.streamlit.io/develop/concepts/custom-components/publish).\nTo import this Streamlit component, follow the same pattern as in the\n`example.py`. Each app can implement its own logic to handle the events coming\nfrom the `quest-board` component.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxiaohk%2Fstreamlit-component-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxiaohk%2Fstreamlit-component-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxiaohk%2Fstreamlit-component-example/lists"}