{"id":36421038,"url":"https://github.com/tschuehly/spring-view-component","last_synced_at":"2026-01-11T17:35:48.139Z","repository":{"id":112743891,"uuid":"604349993","full_name":"tschuehly/spring-view-component","owner":"tschuehly","description":"Server-side UI components with spring boot","archived":false,"fork":false,"pushed_at":"2026-01-03T13:48:57.000Z","size":877,"stargazers_count":230,"open_issues_count":4,"forks_count":17,"subscribers_count":5,"default_branch":"master","last_synced_at":"2026-01-07T09:47:55.383Z","etag":null,"topics":["htmx","jte","kotlin","spring","spring-boot","ssr","thymeleaf"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","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/tschuehly.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":"2023-02-20T21:50:38.000Z","updated_at":"2025-12-01T03:36:50.000Z","dependencies_parsed_at":null,"dependency_job_id":"d48032db-2671-4504-8740-26fc926d8956","html_url":"https://github.com/tschuehly/spring-view-component","commit_stats":null,"previous_names":[],"tags_count":42,"template":false,"template_full_name":null,"purl":"pkg:github/tschuehly/spring-view-component","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tschuehly%2Fspring-view-component","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tschuehly%2Fspring-view-component/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tschuehly%2Fspring-view-component/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tschuehly%2Fspring-view-component/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tschuehly","download_url":"https://codeload.github.com/tschuehly/spring-view-component/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tschuehly%2Fspring-view-component/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28315879,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-11T14:58:17.114Z","status":"ssl_error","status_checked_at":"2026-01-11T14:55:53.580Z","response_time":60,"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":["htmx","jte","kotlin","spring","spring-boot","ssr","thymeleaf"],"created_at":"2026-01-11T17:35:47.619Z","updated_at":"2026-01-11T17:35:48.131Z","avatar_url":"https://github.com/tschuehly.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"![image](https://user-images.githubusercontent.com/33346637/235085980-eb16eaa3-ec89-4293-9609-cf651a44f60e.png)\n\nSpring ViewComponent allows you to create typesafe, reusable \u0026 encapsulated server-rendered UI components.\n\n##### Table of Contents\n\n- [What’s a ViewComponent?](#whats-a-viewcomponent)\n- [Render a ViewComponent](#render-a-viewcomponent)\n- [Nesting ViewComponents:](#nesting-viewcomponents)\n- [Local Development Configuration](#local-development)\n- [Production Configuration](#production-configuration)\n- [Installation](#installation)\n- [Technical Implementation](#technical-implementation)\n\n## What’s a ViewComponent?\n\nViewComponents consolidate the logic needed for a template into a single class,\nresulting in a cohesive object that is easy to understand. \n([Source: ViewComponent for Rails](https://viewcomponent.org/))\n\nA Spring ViewComponent is a Spring Bean that defines the context for our Template:\n\n\u003cdetails open\u003e\n    \u003csummary\u003eJava\u003c/summary\u003e\n\n```java\n\n@ViewComponent\npublic class SimpleViewComponent {\n\n  public record SimpleView(String helloWorld) implements ViewContext {\n\n  }\n\n  public SimpleView render() {\n    return new SimpleView(\"Hello World\");\n  }\n}\n```\n\n\u003c/details\u003e\n\nWe define the context by creating a record that implements the ViewContext interface\n\nNext, we add the `@ViewComponent` annotation to a class and define a method that returns the `SimpleView` record.\n\n\u003cdetails\u003e\n    \u003csummary\u003eKotlin\u003c/summary\u003e\n\n```kotlin\n// SimpleViewComponent.kt\n@ViewComponent\nclass SimpleViewComponent {\n    fun render() = SimpleView(\"Hello World\")\n\n    data class SimpleView(val helloWorld: String) : ViewContext\n}\n```\n\n\u003c/details\u003e\n\nA ViewComponent always needs a corresponding HTML Template.\nWe define the Template in the SimpleViewComponent.[html/jte/kte] In the same package as our ViewComponent class.\n\nWe can use [Thymeleaf](https://thymeleaf.org)\n\n````html \n// SimpleViewComponent.html\n\u003c!--/*@thymesVar id=\"d\" type=\"de.tschuehly.example.thymeleafjava.web.simple.SimpleViewComponent.SimpleView\"*/--\u003e\n\u003cdiv th:text=\"${simpleView.helloWorld()}\"\u003e\u003c/div\u003e\n````\n\nor [JTE](https://jte.gg/#5-minutes-example)\n\n```html\n// SimpleViewComponent.jte\n@param de.tschuehly.example.jte.web.simple.SimpleViewComponent.SimpleView simpleView\n\u003cdiv\u003e${simpleView.helloWorld()}\u003c/div\u003e\n```\n\nor [KTE](https://jte.gg/#5-minutes-example)\n\n```html\n@param simpleView: de.tschuehly.kteviewcomponentexample.web.simple.SimpleViewComponent.SimpleView\n\u003cdiv\u003e\n  \u003ch2\u003eThis is the SimpleViewComponent\u003c/h2\u003e\n  \u003cdiv\u003e${simpleView.helloWorld}\u003c/div\u003e\n\u003c/div\u003e\n```\n\n## Render a ViewComponent\n\nWe can then call the render method in our controller to render the template.\n\u003cdetails open\u003e\n    \u003csummary\u003eJava\u003c/summary\u003e\n\n```java\n\n@Controller\npublic class SimpleController {\n\n  private final SimpleViewComponent simpleViewComponent;\n\n  public TestController(SimpleViewComponent simpleViewComponent) {\n    this.simpleViewComponent = simpleViewComponent;\n  }\n\n  @GetMapping(\"/\")\n  ViewContext simple() {\n    return simpleViewComponent.render();\n  }\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003eKotlin\u003c/summary\u003e\n\n```kotlin\n// Router.kt\n@Controller\nclass SimpleController(\n    private val simpleViewComponent: SimpleViewComponent,\n) {\n\n    @GetMapping(\"/\")\n    fun simpleComponent() = simpleViewComponent.render()\n}\n```\n\n\u003c/details\u003e\n\n## Examples\n\nIf you want to get started right away you can find examples for all possible language combinations here:\n[Examples](/examples/)\n\n## Nesting ViewComponents:\n\nWe can nest components by passing a ViewContext as property of our record,\nif we also have it as parameter of our render method we can easily create layouts:\n\n\u003cdetails open\u003e\n    \u003csummary\u003eJava\u003c/summary\u003e\n\n```java\n\n@ViewComponent\npublic\nclass LayoutViewComponent {\n\n  private record LayoutView(ViewContext nestedViewComponent) implements ViewContext {\n\n  }\n\n  public ViewContext render(ViewContext nestedViewComponent) {\n    return new LayoutView(nestedViewComponent);\n  }\n}\n```\n\n\u003c/details\u003e\n\u003cdetails \u003e\n    \u003csummary\u003eKotlin\u003c/summary\u003e\n\n```kotlin\n@ViewComponent\nclass LayoutViewComponent {\n    data class LayoutView(val nestedViewComponent: ViewContext) : ViewContext\n\n    fun render(nestedViewComponent: ViewContext) = LayoutView(nestedViewComponent)\n\n}\n```\n\n\u003c/details\u003e\n\n### Thymeleaf\n\nIn Thymeleaf we render the passed ViewComponent with the `view:component=\"${viewContext}\"` attribute.\n\n```html\n\n\u003cnav\u003e\n  This is a navbar\n\u003c/nav\u003e\n\u003c!--/*@thymesVar id=\"layoutView\" type=\"de.tschuehly.example.thymeleafjava.web.layout.LayoutViewComponent.LayoutView\"*/--\u003e\n\u003cdiv view:component=\"${layoutView.nestedViewComponent()}\"\u003e\u003c/div\u003e\n\u003cfooter\u003e\n  This is a footer\n\u003c/footer\u003e\n```\n\n### JTE / KTE\n\nIn JTE/KTE we can just call the LayoutView record directly in an expression:\n\n```html\n@param layoutView: de.tschuehly.kteviewcomponentexample.web.layout.LayoutViewComponent.LayoutView\n\u003cnav\u003e\n  This is a Navbar\n\u003c/nav\u003e\n\u003cbody\u003e\n${layoutView.nestedViewComponent}\n\u003c/body\u003e\n\u003cfooter\u003e\n  This is a footer\n\u003c/footer\u003e\n```\n\n## Local Development Configuration\n\nYou can enable hot-reloading of the templates in development, you need to have Spring Boot DevTools as a dependency.\n\n```properties\nspring.view-component.local-development=true\n```\n\n## Installation\n\n### Thymeleaf:\n\n[LATEST_VERSION](https://central.sonatype.com/artifact/de.tschuehly/spring-view-component-thymeleaf) on Maven Central\n\n\u003cdetails open\u003e\n    \u003csummary\u003eGradle\u003c/summary\u003e\n\n```kotlin\nimplementation(\"de.tschuehly:spring-view-component-thymeleaf:0.9.0\")\nsourceSets {\n    main {\n        resources {\n            srcDir(\"src/main/java\")\n            exclude(\"**/*.java\")\n        }\n    }\n\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003eMaven\u003c/summary\u003e\n\n```xml\n\u003cproject\u003e\n  \u003cdependencies\u003e\n    \u003cdependency\u003e\n      \u003cgroupId\u003ede.tschuehly\u003c/groupId\u003e\n      \u003cartifactId\u003espring-view-component-thymeleaf\u003c/artifactId\u003e\n      \u003cversion\u003e0.9.0\u003c/version\u003e\n    \u003c/dependency\u003e\n  \u003c/dependencies\u003e\n  \u003cbuild\u003e\n    \u003cresources\u003e\n      \u003cresource\u003e\n        \u003cdirectory\u003esrc/main/java\u003c/directory\u003e\n        \u003cincludes\u003e\n          \u003cinclude\u003e**/*.html\u003c/include\u003e\n        \u003c/includes\u003e\n      \u003c/resource\u003e\n      \u003cresource\u003e\n        \u003cdirectory\u003esrc/main/resources\u003c/directory\u003e\n      \u003c/resource\u003e\n    \u003c/resources\u003e\n    \u003cplugins\u003e\n      \u003cplugin\u003e\n        \u003cartifactId\u003emaven-resources-plugin\u003c/artifactId\u003e\n        \u003cversion\u003e3.3.0\u003c/version\u003e\n      \u003c/plugin\u003e\n    \u003c/plugins\u003e\n  \u003c/build\u003e\n\u003c/project\u003e\n```\n\n\u003c/details\u003e\n\n### JTE\n\n[LATEST_VERSION](https://central.sonatype.com/artifact/de.tschuehly/spring-view-component-jte) on Maven Central\n\n\n\u003cdetails open\u003e\n    \u003csummary\u003eGradle\u003c/summary\u003e\n\n```kotlin\nplugins {\n    id(\"gg.jte.gradle\") version(\"3.2.1\")\n}\n\nimplementation(\"de.tschuehly:spring-view-component-jte:0.9.0\")\n\njte{\n    generate()\n    sourceDirectory.set(kotlin.io.path.Path(\"src/main/java\"))\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003eMaven\u003c/summary\u003e\n\n```xml\n\u003cproject \u003e\n  \u003cdependencies\u003e\n    \u003cdependency\u003e\n      \u003cgroupId\u003ede.tschuehly\u003c/groupId\u003e\n      \u003cartifactId\u003espring-view-component-jte\u003c/artifactId\u003e\n      \u003cversion\u003e0.9.0\u003c/version\u003e\n    \u003c/dependency\u003e\n  \u003c/dependencies\u003e\n  \u003cbuild\u003e\n    \u003cplugins\u003e\n      \u003cplugin\u003e\n        \u003cgroupId\u003egg.jte\u003c/groupId\u003e\n        \u003cartifactId\u003ejte-maven-plugin\u003c/artifactId\u003e\n        \u003cversion\u003e3.1.12\u003c/version\u003e\n        \u003cconfiguration\u003e\n          \u003csourceDirectory\u003e${project.basedir}/src/main/java\u003c/sourceDirectory\u003e\n          \u003ccontentType\u003eHtml\u003c/contentType\u003e\n        \u003c/configuration\u003e\n        \u003cexecutions\u003e\n          \u003cexecution\u003e\n            \u003cphase\u003egenerate-sources\u003c/phase\u003e\n            \u003cgoals\u003e\n              \u003cgoal\u003egenerate\u003c/goal\u003e\n            \u003c/goals\u003e\n          \u003c/execution\u003e\n        \u003c/executions\u003e\n      \u003c/plugin\u003e\n    \u003c/plugins\u003e\n  \u003c/build\u003e\n\u003c/project\u003e\n```\n\n\u003c/details\u003e\n\n### KTE\n\n[LATEST_VERSION](https://central.sonatype.com/artifact/de.tschuehly/spring-view-component-kte) on Maven Central\n\n\n\u003cdetails open\u003e\n    \u003csummary\u003eGradle\u003c/summary\u003e\n\n```kotlin\n\nplugins {\n    id(\"gg.jte.gradle\") version (\"3.1.12\")\n}\n\ndependencies {\n    implementation(\"de.tschuehly:spring-view-component-kte:0.9.0\")\n}\n\njte {\n    generate()\n    sourceDirectory.set(kotlin.io.path.Path(\"src/main/kotlin\"))\n}\n```\n\n\u003c/details\u003e\n\n## Technical Implementation\n\nSpring ViewComponent wraps the Spring MVC container using an AspectJ Aspect and automatically resolves the template and puts the ViewContext in the ModelAndViewContainer\n\n![image](https://github.com/tschuehly/spring-view-component/assets/33346637/ad2f2517-7eab-4b07-9249-59aeaae1e778)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftschuehly%2Fspring-view-component","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftschuehly%2Fspring-view-component","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftschuehly%2Fspring-view-component/lists"}