A small Kotlin/Ktor sample that renders HTML on the server and uses htmx for component-style page updates without a separate JavaScript application framework.
This repository is best treated as a compact example or starting point, not as a production-ready web application template.
- Server-rendered HTML with Ktor and
kotlinx.html - Showcase content that explains the Ktor route and htmx fragment-swap model
- htmx-powered navigation for dynamic component swaps
- Active navigation state for direct loads, htmx swaps, and browser history
- Direct component URLs that still render the full application shell on refresh
- Interactive calendar rows that load server-rendered event details
- Tailwind utility classes compiled into a static CSS asset
- Netty-based Ktor server
- Dockerfile-based container packaging for deployment demos
- Route and htmx contract tests with Ktor
testApplication - JaCoCo coverage reporting and verification
- Kotlin
2.3.21 - Ktor
3.5.0 - Gradle wrapper
9.5.1 - Logback
1.5.33 - JaCoCo
0.8.14 - htmx
2.0.10 - Tailwind CSS
4.3.0 - Tailwind CLI
4.3.0
- JDK 17 or 21
- Node.js and npm for rebuilding Tailwind CSS
- No separate Gradle installation is required; use the checked-in Gradle wrapper.
- Docker is optional and only required for container build checks.
The project was last verified locally with OpenJDK 21.0.10.
git clone https://github.com/rf43/htmx-ktor.git
cd htmx-ktor
npm ci
npm run build:css
./gradlew runOpen http://localhost:8080.
Development mode is enabled by default through gradle.properties.
To run without Ktor development mode:
./gradlew run -Pdevelopment=falseRun tests:
./gradlew testThe test suite verifies the root application shell, static asset routing, htmx navigation attributes, component route registration, and route-specific showcase content.
Rebuild the compiled Tailwind CSS asset:
npm run build:cssGradle packages the current src/main/resources/static/app.css; it does not regenerate Tailwind CSS. Run npm run build:css after changing Tailwind classes or src/main/resources/styles/tailwind.css.
Watch Tailwind source inputs during local UI work:
npm run watch:cssBuild the project:
./gradlew buildBuild the container image:
docker build -t htmx-ktor .The Docker build regenerates app.css before packaging the Ktor distribution.
The Docker build runs Kotlin compilation in-process to avoid short-lived Kotlin daemon files during hosted image builds.
Run the container locally:
docker run --rm -p 8080:8080 htmx-ktorThe server reads PORT from the environment and defaults to 8080, which keeps local runs simple while allowing managed platforms to provide the runtime port.
Generate the JaCoCo report:
./gradlew jacocoTestReportVerify coverage:
./gradlew jacocoTestCoverageVerificationThe HTML coverage report is written to build/reports/jacoco/test/html/index.html.
htmx-ktor/
|-- src/
| |-- main/
| | |-- kotlin/io/ivycreek/
| | | |-- about/
| | | |-- calendar/
| | | |-- contact/
| | | |-- content/
| | | |-- dashboard/
| | | |-- navbar/
| | | |-- plugins/
| | | |-- projects/
| | | |-- team/
| | | `-- Application.kt
| | `-- resources/
| | |-- logback.xml
| | |-- styles/
| | `-- static/
| `-- test/kotlin/io/ivycreek/
|-- build.gradle.kts
|-- Dockerfile
|-- package.json
|-- package-lock.json
|-- .dockerignore
|-- gradle.properties
|-- settings.gradle.kts
`-- gradle/wrapper/
Each page package generally has two files:
Feature.ktrenders the HTML fragment.FeatureRouter.ktregisters the/components/...route.
The root page and static resource routing live in src/main/kotlin/io/ivycreek/plugins/Routing.kt.
Component routes return fragments for htmx requests and the full application shell for normal browser requests, so pushed URLs remain refreshable and bookmarkable.
- Tailwind is built with the CLI from
src/main/resources/styles/tailwind.cssintosrc/main/resources/static/app.css, which Ktor serves at/app.css. - The project currently loads htmx from a pinned 2.x CDN URL with subresource integrity.
This project is licensed under the MIT License. See LICENSE for details.