{"id":13537206,"url":"https://github.com/dropbox/componentbox","last_synced_at":"2025-08-03T10:04:23.801Z","repository":{"id":37268409,"uuid":"464561169","full_name":"dropbox/componentbox","owner":"dropbox","description":"Reactive server-driven UI for iOS, Android, and web","archived":true,"fork":false,"pushed_at":"2024-03-06T21:55:31.000Z","size":40226,"stargazers_count":259,"open_issues_count":1,"forks_count":6,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-03-26T15:16:48.749Z","etag":null,"topics":["android","compose","ios","jetpack","kotlin-multiplatform","react","server-driven-ui","swift-ui"],"latest_commit_sha":null,"homepage":"https://dropbox.github.io/componentbox/","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dropbox.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","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}},"created_at":"2022-02-28T16:34:29.000Z","updated_at":"2024-12-11T11:08:59.000Z","dependencies_parsed_at":"2024-03-06T22:57:18.966Z","dependency_job_id":null,"html_url":"https://github.com/dropbox/componentbox","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/dropbox/componentbox","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dropbox%2Fcomponentbox","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dropbox%2Fcomponentbox/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dropbox%2Fcomponentbox/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dropbox%2Fcomponentbox/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dropbox","download_url":"https://codeload.github.com/dropbox/componentbox/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dropbox%2Fcomponentbox/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268525073,"owners_count":24264095,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-03T02:00:12.545Z","response_time":2577,"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":["android","compose","ios","jetpack","kotlin-multiplatform","react","server-driven-ui","swift-ui"],"created_at":"2024-08-01T09:00:56.282Z","updated_at":"2025-08-03T10:04:23.515Z","avatar_url":"https://github.com/dropbox.png","language":"Kotlin","funding_links":[],"categories":["Libraries","Kotlin"],"sub_categories":["GUI"],"readme":"\u003cimg src=\"https://user-images.githubusercontent.com/59468153/226168774-5c086794-652b-4bf9-bc67-f1dc4b86d81f.svg\" alt=\"componentbox\" width=220/\u003e\n\n# Component Box\n\n\u003e [!NOTE]\n\u003e\n\u003e **No longer active!**\n\u003e \n\u003e After internal experimentation and discussion, we've decided to move forward with other options.\n\n### Sample\n\n#### Model (server)\n\n```kotlin\nclass Counter : ComposableModel\u003cInt, CounterEvent\u003e(0) {\n    private fun increment() {\n        withState {\n            setState(state.value + 1)\n        }\n    }\n\n    private fun decrement() {\n        withState {\n            setState(state.value - 1)\n        }\n    }\n\n    override fun on(event: CounterEvent) = when (event) {\n        Increment -\u003e increment()\n        Decrement -\u003e decrement()\n    }\n}\n```\n\n#### UI Representation (server)\n\n##### Static\n\n###### [Tree](componentbox/src/commonMain/kotlin/com/dropbox/componentbox/Tree.kt)\n\n```kotlin\n@SerializableComponentBox\nfun main() = componentBox {\n        tree {\n            lazyColumn\u003cCounterEvent\u003e(\n                verticalArrangement = Arrangement.SpaceEvenly(2.dp),\n                horizontalAlignment = Alignment.Start\n            ) {\n                child(header)\n                child(count)\n                child(incrementButton)\n                child(decrementButton)\n            }\n        }\n    }\n\nval header = text(\n    text = \"Component Box Counter\",\n    style = TextStyle(fontWeight = FontWeight.ExtraBold)\n)\n\nval count = text(\n    text = \"Count: \\${COUNTER_STATE}\",\n    style = TextStyle(color = Color.Hex(\"#FF0000\"))\n)\n\nval incrementButton = textButton(\n    text = \"+1\",\n    onClick = semantic { Increment }\n)\n\nval decrementButton = textButton(\n    text = \"-1\",\n    onClick = semantic { Decrement }\n)\n```\n\n##### Dynamic\n\n###### [Graph](componentbox/src/commonMain/kotlin/com/dropbox/componentbox/Graph.kt)\n\n```kotlin\n@Composable\n@ComponentBoxExport\nfun main() = statefulComponentBoxGraph(init = null) {\n        Graph(start = CounterOnboardingFlow.value) {\n            componentBox(CounterLoginScreen.value, LoginScreen())\n            componentBox(CounterOnboardingFlow.value, OnboardingFlow())\n            componentBox(CounterScreen.Home.value, HomeScreen())\n        }\n    }\n```\n\n###### [Forest](componentbox/src/commonMain/kotlin/com/dropbox/componentbox/Forest.kt)\n\n```kotlin\n@Composable\nfun LoginScreen() = Forest {\n        tree(\"heading\", Tree { LoginHeading() })\n        tree(\"button\", Tree { LoginButton() })\n    }\n```\n\n###### [Trail](componentbox/src/commonMain/kotlin/com/dropbox/componentbox/Trail.kt)\n\n```kotlin\n@Composable\nfun OnboardingFlow() = Trail {\n        node(WelcomeScreen())\n        node(FeatureDiscoveryScreen())\n        node(HomeScreen())\n    }\n```\n\n###### [Tree](componentbox/src/commonMain/kotlin/com/dropbox/componentbox/Tree.kt)\n\n```kotlin\n@Composable\nfun HomeScreen() = ComponentBox {\n        Tree {\n            LazyColumn(\n                verticalArrangement = Arrangement.SpaceEvenly(2.dp),\n                horizontalAlignment = Alignment.Start\n            ) {\n                child(header)\n                child(Count())\n                child(IncrementButton())\n                child(DecrementButton())\n            }\n        }\n    }\n\n@Composable\nfun IncrementButton() = StatefulComposable\u003cCounter\u003e {\n    ContainedButton(onClick = lambda { it.on(Increment) }) {\n        text(text = \"+1\")\n    }\n}\n\n@Composable\nfun DecrementButton() = StatefulComposable\u003cCounter\u003e {\n    ContainedButton(onClick = lambda { it.on(Decrement) }) {\n        text(text = \"-1\")\n    }\n}\n\n@Composable\nfun Count() = StatefulComposable\u003cCounter\u003e {\n    text(\n        text = \"Count: ${it.state.value}\",\n        style = TextStyle(color = Color.Hex(\"#FF0000\"))\n    )\n}\n\n\n```\n\n#### Binaries (server)\n\n```shell\n./gradlew componentBoxJs\n```\n\n```shell\n./gradlew componentBoxJson\n```\n\n#### Jetpack Compose (Android)\n\n##### Activity\n\n```kotlin\nclass ComponentBoxActivity : ComponentActivity() {\n    private val scope = CoroutineScope(Dispatchers.Default)\n    private val service = ComponentBoxService(scope)\n    private val componentBox = service.componentBox\n    private val render = RenderingEngine()\n\n    override fun onStart() {\n        super.onStart()\n        service.launch(MANIFEST_URL)\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        setContent {\n            val root = componentBox.collectAsState()\n            render {\n                root.value\n            }\n        }\n    }\n}\n```\n\n##### Composable\n\n```kotlin\n@Composable\nfun ComponentBoxView(componentBox: StateFlow\u003cComponent?\u003e, render: RenderingEngine) {\n    val root = componentBox.collectAsState()\n    render {\n        root.value\n    }\n}\n```\n\n#### React (web)\n\n```js\nexport default function ComponentBoxView(props: {manifestUrl: string}) {\n  const [root, setRoot] = useState\u003cComponent | null\u003e(null);\n  const service = new ComponentBoxService();\n  const render = new RenderingEngine();\n\n  useEffect(() =\u003e {\n    async function launch(manifestUrl: string): Tree {\n        const componentBox = await service.launch(manifestUrl)\n        setRoot(componentBox.root)\n    }\n    \n    launch(props.manifestUrl)\n \n  }, [props.manifestUrl]);\n  \n  return render(root)\n}\n```\n\n#### SwiftUI (iOS)\n\n```swift\nstruct ComponentBoxView: View {\n    @StateObject private var service = ComponentBoxService()\n    @State private var root: Component?\n    \n    var body: some View {\n        render {\n            root\n        }\n        .onAppear {\n            service.launch(from: MANIFEST_URL) { result in\n                switch result {\n                case .success(let componentBox):\n                    DispatchQueue.main.async {\n                        self.root = componentBox.root\n                    }\n            }\n        }\n    }\n}\n```\n\n## Snapshots\n\nSnapshots are available\nin [Sonatype's snapshots repository](https://s01.oss.sonatype.org/content/repositories/snapshots/com/dropbox/componentbox/).\n\n## License\n\n```text\nCopyright (c) 2023 Dropbox, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n\n## Acknowledgments\n| [\u003cimg src=\"https://avatars.githubusercontent.com/u/49219790?s=200\u0026v=4\" alt=\"cashapp\" width=\"25px\"/\u003e](https://github.com/cashapp) | Thanks to our friends at [Cash App](http://github.com/cashapp) for [Zipline](http://github.com/cashapp/zipline) |\n|:-:|---|\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdropbox%2Fcomponentbox","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdropbox%2Fcomponentbox","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdropbox%2Fcomponentbox/lists"}