diff --git a/docs/3.config/0.index.md b/docs/3.config/0.index.md
index 997496d3a8..dd1cd05055 100644
--- a/docs/3.config/0.index.md
+++ b/docs/3.config/0.index.md
@@ -40,7 +40,10 @@ Enable experimental features.
#### `openAPI`
-Enable `/_nitro/swagger` and `/_nitro/openapi.json` endpoints.
+Enable `/_nitro/scalar` and `/_nitro/swagger` and `/_nitro/openapi.json` endpoints.
+
+> [!IMPORTANT]
+> [Scalar](https://scalar.com/) support is currently available in [nightly channel](/guide/nightly).
#### `wasm`
diff --git a/playground/nitro.config.ts b/playground/nitro.config.ts
index a763adbd67..2a9dd131e6 100644
--- a/playground/nitro.config.ts
+++ b/playground/nitro.config.ts
@@ -1 +1 @@
-export default defineNitroConfig({});
\ No newline at end of file
+export default defineNitroConfig({});
diff --git a/src/options.ts b/src/options.ts
index 4198e5b2b2..e6ec41b9cc 100644
--- a/src/options.ts
+++ b/src/options.ts
@@ -387,6 +387,10 @@ export async function loadOptions(
route: "/_nitro/openapi.json",
handler: "#internal/nitro/routes/openapi",
});
+ options.handlers.push({
+ route: "/_nitro/scalar",
+ handler: "#internal/nitro/routes/scalar",
+ });
options.handlers.push({
route: "/_nitro/swagger",
handler: "#internal/nitro/routes/swagger",
diff --git a/src/runtime/routes/scalar.ts b/src/runtime/routes/scalar.ts
new file mode 100644
index 0000000000..b22a314f8b
--- /dev/null
+++ b/src/runtime/routes/scalar.ts
@@ -0,0 +1,183 @@
+import { eventHandler } from "h3";
+
+// https://github.com/scalar/scalar
+
+// Served as /_nitro/scalar
+export default eventHandler((event) => {
+ const title = "Nitro Scalar API Reference";
+
+ return /* html */ `
+
+
+
+
+
+ ${title}
+
+
+
+
+
+
+ `;
+});
+
+const customTheme = /* css */ `/* basic theme */
+ .light-mode,
+ .light-mode .dark-mode {
+ --theme-background-1: #fff;
+ --theme-background-2: #fafafa;
+ --theme-background-3: rgb(245 245 245);
+
+ --theme-color-1: #2a2f45;
+ --theme-color-2: #757575;
+ --theme-color-3: #8e8e8e;
+
+ --theme-color-accent: #ef4444;
+ --theme-background-accent: transparent;
+
+ --theme-border-color: rgba(0, 0, 0, 0.1);
+ }
+ .dark-mode {
+ --theme-background-1: #171717;
+ --theme-background-2: #262626;
+ --theme-background-3: #2e2e2e;
+
+ --theme-color-1: rgba(255, 255, 255, 0.9);
+ --theme-color-2: rgba(255, 255, 255, 0.62);
+ --theme-color-3: rgba(255, 255, 255, 0.44);
+
+ --theme-color-accent: #f87171;
+ --theme-background-accent: transparent;
+
+ --theme-border-color: rgba(255, 255, 255, 0.1);
+ }
+
+ /* Document Sidebar */
+ .light-mode .t-doc__sidebar,
+ .dark-mode .t-doc__sidebar {
+ --sidebar-background-1: var(--theme-background-1);
+ --sidebar-color-1: var(--theme-color-1);
+ --sidebar-color-2: var(--theme-color-2);
+ --sidebar-border-color: var(--theme-border-color);
+
+ --sidebar-item-hover-background: transparent;
+ --sidebar-item-hover-color: var(--sidebar-color-1);
+
+ --sidebar-item-active-background: var(--theme-background-accent);
+ --sidebar-color-active: var(--theme-color-accent);
+
+ --sidebar-search-background: transparent;
+ --sidebar-search-color: var(--theme-color-3);
+ --sidebar-search-border-color: var(--theme-border-color);
+ }
+
+ /* advanced */
+ .light-mode .dark-mode,
+ .light-mode {
+ --theme-color-green: #91b859;
+ --theme-color-red: #e53935;
+ --theme-color-yellow: #e2931d;
+ --theme-color-blue: #6182b8;
+ --theme-color-orange: #f76d47;
+ --theme-color-purple: #9c3eda;
+ }
+ .dark-mode {
+ --theme-color-green: #c3e88d;
+ --theme-color-red: #f07178;
+ --theme-color-yellow: #ffcb6b;
+ --theme-color-blue: #82aaff;
+ --theme-color-orange: #f78c6c;
+ --theme-color-purple: #c792ea;
+ }
+ /* custom-theme */
+ .section-container:nth-of-type(2)
+ ~ .section-container
+ .scalar-card
+ .scalar-card-header {
+ --theme-background-2: var(--theme-background-1) !important;
+ }
+ .section-flare {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ animation: spin 39s linear infinite;
+ transition: all 0.3s ease-in-out;
+ opacity: 1;
+ }
+ .section-flare-item:nth-of-type(1),
+ .section-flare-item:nth-of-type(2),
+ .section-flare-item:nth-of-type(3) {
+ content: "";
+ width: 1000px;
+ height: 1000px;
+ position: absolute;
+ top: 0;
+ right: 0;
+ border-radius: 50%;
+ background: var(--default-theme-background-1);
+ display: block;
+ opacity: 1;
+ filter: blur(48px);
+ -webkit-backface-visibility: hidden;
+ -webkit-perspective: 1000;
+ -webkit-transform: translate3d(0, 0, 0);
+ -webkit-transform: translateZ(0);
+ perspective: 1000;
+ transform: translate3d(0, 0, 0);
+ transform: translateZ(0);
+ }
+ .section-flare-item:nth-of-type(2) {
+ top: initial;
+ right: initial;
+ background: #ef4444;
+ width: 700px;
+ height: 700px;
+ opacity: 0.3;
+ animation: sectionflare 37s linear infinite;
+ }
+ .section-flare-item:nth-of-type(1) {
+ top: initial;
+ right: initial;
+ bottom: 0;
+ left: -20px;
+ background: #ef4444;
+ width: 500px;
+ height: 500px;
+ opacity: 0.3;
+ animation: sectionflare2 38s linear infinite;
+ }
+ @keyframes sectionflare {
+ 0%,
+ 100% {
+ transform: translate3d(0, 0, 0);
+ }
+ 50% {
+ transform: translate3d(525px, 525px, 0);
+ }
+ }
+ @keyframes sectionflare2 {
+ 0%,
+ 100% {
+ transform: translate3d(700px, 700px, 0);
+ }
+ 50% {
+ transform: translate3d(0, 0, 0);
+ }
+ }
+ @keyframes spin {
+ 100% {
+ transform: rotate(360deg);
+ }
+ }
+ .section-container:nth-of-type(2) {
+ overflow: hidden;
+ }`;
diff --git a/src/runtime/routes/swagger.ts b/src/runtime/routes/swagger.ts
index 6750bd6ab6..46cec5896e 100644
--- a/src/runtime/routes/swagger.ts
+++ b/src/runtime/routes/swagger.ts
@@ -6,7 +6,7 @@ import { eventHandler } from "h3";
export default eventHandler((event) => {
const title = "Nitro Swagger UI";
const CDN_BASE = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@^4";
- return html`
+ return /* html */ `
@@ -38,7 +38,3 @@ export default eventHandler((event) => {
`;
});
-
-function html(str, ...args) {
- return String.raw(str, ...args);
-}