Skip to main content

Enhance Docusaurus Documentation with SDK Code Snippets using liblab's Enhanced API Spec

This tutorial will guide you through integrating the enhanced OpenAPI spec generated by liblab into your Docusaurus documentation site. By using liblab, you'll be able to improve your API reference pages with SDK code snippets and examples across multiple programming languages, improving the overall developer experience.

Following these steps, you'll create a new Docusaurus project, configure API reference pages, generate an enhanced OpenAPI spec, and customize the Docusaurus components to include SDK snippets.

Prerequisites

Before you start, ensure you have:

Steps

  1. Create a New Docusaurus Project
  2. Create the API Reference Pages
  3. Create the liblab Project
  4. Enhance the OpenAPI
  5. Create a Wrapper for the API Components
  6. Test the SDKs Documentation

1. Create a New Docusaurus Project

To set up your documentation project, use the Docusaurus CLI and the liblab template:

  1. Run the following command to create the project. Replace <your-project-name> with the desired name of your documentation project:

    npx [email protected] <your-project-name> --package-manager yarn
  2. When prompted, select Git repository as the template source, then provide this GitHub repository URL as the template:

    https://github.com/liblaber/docusaurus-liblab-template.git
  3. For the cloning option, choose Copy: do a shallow clone, but do not create a git repo.

  4. After cloning, navigate to the project directory and install dependencies with:

    cd <your-project-name>
    npm install
  5. To preview your project locally, run:

    npm run start

Once started, the project will open in your browser at http://localhost:3000/. You'll see an initial preview, as shown in the image below.

First preview of your documentation using the liblab template

2. Create the API Reference Pages

Now, you'll add API content to the documentation site. The template from Step 1 includes the Docusaurus OpenAPI plugin, which enables you to generate API documentation from an OpenAPI spec.

2.1 Add the OpenAPI Spec

For this tutorial, we'll use the PetStore API, but you can substitute it with your own API spec.

  1. Create a directory called static/api-spec in the project root and add an openapi.json file with the PetStore OpenAPI Spec or your own OpenAPI spec.

  2. Open docusaurus.config.js and update it to use the openapi.json file to generate the endpoint pages. Set petstore.specPath to ./static/api-spec/openapi.json as shown:

    docusaurus.config.js
    plugins: [
    [
    "docusaurus-plugin-openapi-docs",
    {
    id: "openapi",
    docsPluginId: "classic",
    config: {
    petstore: {
    specPath: "./static/api-spec/openapi.json",
    outputDir: "docs/petstore",
    sidebarOptions: {
    groupPathsBy: "tag",
    categoryLinkSource: "tag",
    },
    } satisfies OpenApiPlugin.Options,
    } satisfies Plugin.PluginOptions,
    },
    ],
    ],

This configuration tells the plugin to generate API docs in the docs/petstore directory.

2.2 Generate the API Reference Pages

To create the API endpoint pages in the documentation, run these commands from the root of your project:

Terminal
npm run docusaurus clean-api-docs all
npm run docusaurus gen-api-docs all

These commands generate a new petstore folder in the docs directory, containing documentation pages for each endpoint. Below is an example of the new file structure:

docs directory
├── docs/
├── intro.md
├── petstore/
│ ├── add-pet.api.mdx
│ ├── create-user.api.mdx
│ ├── create-users-with-list-input.api.mdx
│ ├── delete-order.api.mdx
│ ├── delete-pet.api.mdx
│ ├── delete-user.api.mdx
│ ├── find-pets-by-status.api.mdx
│ ├── find-pets-by-tags.api.mdx
│ ├── get-inventory.api.mdx
│ ├── get-order-by-id.api.mdx
│ ├── get-pet-by-id.api.mdx
│ ├── get-user-by-name.api.mdx
│ ├── login-user.api.mdx
│ ├── logout-user.api.mdx
│ ├── place-order.api.mdx
│ ├── pet.tag.mdx
│ ├── sidebar.ts
│ ├── store.tag.mdx
│ ├── swagger-petstore-openapi-3-0.info.mdx
│ ├── update-pet.api.mdx
│ ├── update-pet-with-form.api.mdx
│ ├── update-user.api.mdx
│ ├── upload-file.api.mdx
│ └── user.tag.mdx
├── tutorial-basics/
└── tutorial-extras/

2.3 Update the Documentation Structure

To make the API endpoint pages accessible, you'll need to update sidebars.ts and docusaurus.config.ts:

Edit the sidebars.ts file at the project root to include the PetStore endpoints. A file with the sidebar structure for the Petstore documentation was generated in Step 2.2. You have to reference it in sidebars.ts as shown:

sidebars.ts
import type { SidebarsConfig } from "@docusaurus/plugin-content-docs";

const sidebars: SidebarsConfig = {
tutorialSidebar: [
{ type: "doc", id: "intro" },
{ type: "autogenerated", dirName: "tutorial-basics" },
{ type: "autogenerated", dirName: "tutorial-extras" },
],
openApiSidebar: [
{
type: "category",
label: "Petstore",
link: {
type: "generated-index",
title: "Petstore API",
slug: "/category/petstore-api",
},
items: require("./docs/petstore/sidebar.js"),
},
],
};

export default sidebars;
  1. Add a new tab to the navbar for the PetStore API reference. In docusaurus.config.ts, add a new item under navbar:

    docusaurus.config.ts
    navbar: {
    ...
    items: [
    {
    type: "doc",
    docId: "intro",
    position: "left",
    label: "Tutorial",
    },
    { to: "/blog", label: "Blog", position: "left" },
    {
    label: "Petstore API",
    position: "left",
    to: "/docs/category/petstore-api",
    },
    {
    href: "https://github.com/facebook/docusaurus",
    label: "GitHub",
    position: "right",
    },
    ],
    },
  2. Save your changes and restart the documentation project:

    Terminal
    npm run start

After restarting, access your documentation at http://localhost:3000/. A new Petstore API tab will appear in the navbar. Select it to view the API docs in the sidebar. Expand the Petstore section to reveal an introduction page and documentation for each endpoint/HTTP method combination.

The docs site showing the endpoints under the API sidebar

These generated pages are based on the PetStore OpenAPI spec. The Introduction page displays the info/description from the spec. Code snippets on the right show examples in various programming languages for calling the API.

The code snippets will be replaced with enhanced OpenAPI content when the liblab integration is complete.

3. Create the liblab Project

Now, you'll use liblab to create an enhanced version of your OpenAPI spec. liblab generates SDKs based on your OpenAPI spec and improves it with features like code snippets and SDK examples, enhancing the developer experience with more clear documentation. This enhanced OpenAPI spec will be used to generate a new version of the endpoint documentation pages created in Step 2.

Enhance the OpenAPI Spec

Access the API and SDK documentation overview for more details on how liblab enhances the OpenAPI spec.

To begin, initialize a liblab project in a new directory using the following command:

liblab init

A new liblab.config.json file will be created in the directory. Update this file to reference the same OpenAPI spec used in Step 2.1. For this tutorial, we'll continue using the PetStore API:

liblab.config.json
{
"sdkName": "petstore",
"apiName": "petstore",
"specFilePath": "https://petstore3.swagger.io/api/v3/openapi.json"
...
}

By default, liblab generates SDKs in the following programming languages when you initialize a new project:

  • Java
  • Go
  • Python
  • TypeScript

You can customize this list of languages in your liblab.config.json file. The languages specified here will determine which SDK code snippets are included in the enhanced OpenAPI spec. This enhanced spec will combine your OpenAPI definition with code snippets for each specified language, making it easier for developers to use the API.

Locate the languageOptions section in liblab.config.json to adjust the languages. Remove any configurations for languages you don't need. Check the liblab config file documentation for additional configuration options.

As the last configuration step, ensure liblab is set to generate the enhanced OpenAPI spec by adding the following configuration to liblab.config.json:

liblab.config.json
{
...
"docs": [
"enhancedApiSpec"
],
...
}
Enhanced OpenAPI Spec

For more details on the configuration options and structure used by the enhanced OpenAPI spec generated by liblab, access the API and SDK documentation page.

4. Enhance the OpenAPI

Once you've created the liblab project, build it to generate the SDKs and the enhanced OpenAPI spec. Run the following command:

liblab build -y

liblab will generate the SDKs and the enhanced OpenAPI spec, which you'll find in the output directory.

Copy the content from the enhanced OpenAPI file (enhancedApiSpec.json) and replace the content of the static/api-spec/openapi.json file created in Step 2.1. This updated OpenAPI file now includes SDK code snippets and examples.

To display this enhanced content in your documentation portal, you must update the components that render the OpenAPI spec. The next section addresses this process.

5. Create a Wrapper for the API Components

Docusaurus is built on React, a component-based library for creating websites. Its UI consists of components that can be styled, nested, and customized.

To use the enhanced OpenAPI spec in Docusaurus, you'll need to “swizzle” two components:

  • APIExplorer/CodeSnippets to incorporate the SDK code snippets generated by liblab directly into your documentation.
  • APIExplorer/CodeTabs to control and restrict the displayed programming languages to only those generated with liblab SDKs.
Swizzling

Swizzling is a Docusaurus feature that lets you wrap or replace components. For more details, access the Docusaurus documentation on swizzling.

5.1 Swizzle the CodeSnippets Component

To swizzle the APIExplorer/CodeSnippets component, follow these steps:

  1. In the root of your documentation project, run the following command:

    npm run swizzle docusaurus-theme-openapi-docs ApiExplorer/CodeSnippets -- --wrap --typescript
  2. When prompted, select YES: I know what I am doing!:

    ? Do you really want to swizzle this unsafe internal component? 
    › - Use arrow-keys. Return to submit.
    NO: cancel and stay safe
    ❯ YES: I know what I am doing!
    [Exit]

    This creates a wrapper for the component in the src/theme folder:

    src
    └── theme
    └── APIExplorer
    └── CodeSnippets
    └── index.tsx
  3. Replace the content in the index.tsx file with the following:

    src/theme/APIExplorer/CodeSnippets/index.tsx
      import React, { useState, useEffect } from "react";
    import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
    import ApiCodeBlock from "@theme/ApiExplorer/ApiCodeBlock";
    import CodeTabs from "@theme/ApiExplorer/CodeTabs";
    import buildPostmanRequest from "@theme/ApiExplorer/buildPostmanRequest";
    import { useTypedSelector } from "@theme/ApiItem/hooks";
    import codegen from "postman-code-generators";
    import sdk from "postman-collection";
    import { CodeSampleLanguage } from "./code-snippets-types";

    export interface CodeSample {
    source: string;
    lang: CodeSampleLanguage;
    label?: string;
    }

    export interface Language {
    highlight: string;
    language: string;
    codeSampleLanguage?: string;
    logoClass: string;
    options: {
    followRedirect?: boolean;
    trimRequestBody?: boolean;
    longFormat?: boolean;
    ES6_enabled?: boolean;
    };
    variant: string;
    variants: string[];
    enabled?: boolean;
    }

    export interface Props {
    codeSamples: CodeSample[];
    postman?: sdk.Request;
    }

    function CodeTab({ children, hidden, className }: any): JSX.Element {
    return (
    <div role="tabpanel" className={className} {...{ hidden }}>
    {children}
    </div>
    );
    }

    // Default language configurations from original script
    const defaultLanguages: Language[] = [
    {
    highlight: "bash",
    language: "curl",
    codeSampleLanguage: "bash",
    logoClass: "bash",
    options: {
    longFormat: false,
    followRedirect: true,
    trimRequestBody: true,
    },
    variant: "cURL",
    variants: ["curl"],
    enabled: true,
    },
    {
    highlight: "ruby",
    language: "ruby",
    codeSampleLanguage: "Ruby",
    logoClass: "ruby",
    options: {
    followRedirect: true,
    trimRequestBody: true,
    },
    variant: "Net::HTTP",
    variants: ["net::http"],
    enabled: false
    },
    {
    highlight: "powershell",
    language: "powershell",
    codeSampleLanguage: "PowerShell",
    logoClass: "powershell",
    options: {
    followRedirect: true,
    trimRequestBody: true,
    },
    variant: "RestMethod",
    variants: ["restmethod"],
    enabled: false
    },
    ];

    function CodeSnippets({ codeSamples, postman }: Props) {
    const { siteConfig } = useDocusaurusContext();
    const validCodeSamples = codeSamples.filter((sample) => sample.lang);
    const [selectedSample, setSelectedSample] = useState<string | undefined>(
    validCodeSamples[0]?.lang
    );
    const [generatedCode, setGeneratedCode] = useState<Record<string, string>>({});

    // Get theme configuration for languages
    const userDefinedLanguages =
    (siteConfig?.themeConfig?.languageTabs as Language[] | undefined) ?? [];

    // Merge default and user-defined languages
    const mergedLanguages = defaultLanguages.map(defaultLang => {
    const userLang = userDefinedLanguages.find(
    lang => lang.language === defaultLang.language
    );

    // If user has defined this language, merge its properties
    if (userLang) {
    return {
    ...defaultLang,
    ...userLang,
    options: { ...defaultLang.options, ...userLang.options },
    enabled: userLang.enabled ?? defaultLang.enabled
    };
    }
    return defaultLang;
    });

    // Add any additional user-defined languages
    const additionalLanguages = userDefinedLanguages.filter(
    lang => !defaultLanguages.some(defaultLang => defaultLang.language === lang.language)
    );

    // Combine all languages and filter enabled ones
    const activeLanguages = [...mergedLanguages, ...additionalLanguages]
    .filter(lang => lang.enabled !== false);

    // Get required state from Redux store if postman is provided
    const contentType = useTypedSelector((state: any) => state.contentType.value);
    const accept = useTypedSelector((state: any) => state.accept.value);
    const server = useTypedSelector((state: any) => state.server.value);
    const body = useTypedSelector((state: any) => state.body);
    const pathParams = useTypedSelector((state: any) => state.params.path);
    const queryParams = useTypedSelector((state: any) => state.params.query);
    const cookieParams = useTypedSelector((state: any) => state.params.cookie);
    const headerParams = useTypedSelector((state: any) => state.params.header);
    const auth = useTypedSelector((state: any) => state.auth);

    // Generate code for configured languages if postman request is provided
    useEffect(() => {
    if (postman) {
    const postmanRequest = buildPostmanRequest(postman, {
    queryParams,
    pathParams,
    cookieParams,
    contentType,
    accept,
    headerParams,
    body,
    server,
    auth,
    });

    // Generate code for each enabled language
    activeLanguages.forEach((lang) => {
    codegen.convert(
    lang.language,
    lang.variant,
    postmanRequest,
    lang.options,
    (error: any, snippet: string) => {
    if (!error) {
    setGeneratedCode(prev => ({
    ...prev,
    [lang.language]: snippet
    }));
    }
    }
    );
    });
    }
    }, [
    postman,
    activeLanguages,
    queryParams,
    pathParams,
    cookieParams,
    contentType,
    accept,
    headerParams,
    body,
    server,
    auth,
    ]);

    if (validCodeSamples.length === 0 && !postman) {
    return null;
    }

    // Combine custom code samples with generated code
    const generatedSamples = postman
    ? Object.entries(generatedCode).map(([language, source]) => {
    const langConfig = activeLanguages.find(l => l.language === language);
    return {
    lang: language as CodeSampleLanguage,
    source,
    label: langConfig?.codeSampleLanguage || langConfig?.variant || language
    };
    })
    : [];

    const allSamples = [...generatedSamples, ...validCodeSamples];

    return (
    <>
    <CodeTabs
    groupId="code-samples"
    action={{
    setSelectedSample: setSelectedSample,
    }}
    languageSet={allSamples.map((sample) => ({
    language: sample.lang,
    label: sample.label || sample.lang,
    }))}
    lazy
    >
    {allSamples.map((sample) => (
    <CodeTab
    value={sample.lang}
    label={sample.label || sample.lang}
    key={sample.lang}
    attributes={{
    className: `openapi-tabs__code-item--${sample.lang
    .toLowerCase()
    .replace("#", "sharp")
    .replace("curl", "bash")
    .replace("typescript", "nodejs")
    }`,
    }}
    >
    <ApiCodeBlock
    language={sample.lang.toLocaleLowerCase()}
    className="openapi-explorer__code-block"
    showLineNumbers={true}
    >
    {sample.source}
    </ApiCodeBlock>
    </CodeTab>
    ))}
    </CodeTabs>
    </>
    );
    }

    export default CodeSnippets;

  4. Create three additional files in the CodeSnippets/ directory to support the customized component. Copy and paste the following content into each respective file:

    code-snippets-types.ts
      // https://github.com/github-linguist/linguist/blob/master/lib/linguist/popular.yml
    export type CodeSampleLanguage =
    | "C"
    | "C#"
    | "C++"
    | "CoffeeScript"
    | "CSS"
    | "Dart"
    | "DM"
    | "Elixir"
    | "Go"
    | "Groovy"
    | "HTML"
    | "Java"
    | "JavaScript"
    | "Kotlin"
    | "Objective-C"
    | "Perl"
    | "PHP"
    | "PowerShell"
    | "Python"
    | "Ruby"
    | "Rust"
    | "Scala"
    | "Shell"
    | "Swift"
    | "TypeScript";

    export interface Language {
    highlight: string;
    language: string;
    codeSampleLanguage: CodeSampleLanguage;
    logoClass: string;
    variant: string;
    variants: string[];
    options?: { [key: string]: boolean };
    sample?: string;
    samples?: string[];
    samplesSources?: string[];
    samplesLabels?: string[];
    }

    // https://redocly.com/docs/api-reference-docs/specification-extensions/x-code-samples
    export interface CodeSample {
    source: string;
    lang: CodeSampleLanguage;
    label?: string;
    }

The CodeSnippets directory should look like this:

src
└── theme
└── APIExplorer
└── CodeSnippets
├── index.tsx
├── code-snippets-types.ts
├── languages.json
└── languages.ts

5.2 Swizzle the CodeTabs Component

To ensure only programming languages with SDK examples are available in your documentation, swizzle the CodeTabs component:

  1. In the root of your documentation project, run:

    npm run swizzle docusaurus-theme-openapi-docs ApiExplorer/CodeTabs -- --wrap --typescript
  2. When prompted, select YES: I know what I am doing!:

    ? Do you really want to swizzle this unsafe internal component? 
    › - Use arrow-keys. Return to submit.
    NO: cancel and stay safe
    ❯ YES: I know what I am doing!
    [Exit]

    This creates a wrapper in the src/theme folder:

    src
    └── theme
    └── APIExplorer
    └── CodeTabs
    └── index.tsx
  3. Replace the content in CodeTabs/index.tsx with:

src/theme/APIExplorer/CodeTabs/index.tsx
  import React, { cloneElement, ReactElement } from "react";

import {
sanitizeTabsChildren,
type TabProps,
useScrollPositionBlocker,
useTabs,
} from "@docusaurus/theme-common/internal";
import { TabItemProps } from "@docusaurus/theme-common/lib/utils/tabsUtils";
import useIsBrowser from "@docusaurus/useIsBrowser";
import { Language } from "@theme/ApiExplorer/CodeSnippets";
import clsx from "clsx";

export interface Props {
action: {
[key: string]: React.Dispatch<any>;
};
currentLanguage: Language;
languageSet: Language[];
includeVariant: boolean;
}

export interface CodeTabsProps extends Props, TabProps {
includeSample?: boolean;
}

function TabList({
action,
currentLanguage,
languageSet,
includeVariant,
includeSample,
className,
block,
selectedValue,
selectValue,
tabValues,
}: CodeTabsProps & ReturnType<typeof useTabs>) {
const tabRefs: (HTMLLIElement | null)[] = [];
const { blockElementScrollPositionUntilNextRender } =
useScrollPositionBlocker();

const handleTabChange = (
event:
| React.FocusEvent<HTMLLIElement>
| React.MouseEvent<HTMLLIElement>
| React.KeyboardEvent<HTMLLIElement>
) => {
const newTab = event.currentTarget;
const newTabIndex = tabRefs.indexOf(newTab);
const newTabValue = tabValues[newTabIndex]!.value;

if (newTabValue !== selectedValue) {
blockElementScrollPositionUntilNextRender(newTab);
selectValue(newTabValue);
}
};

const handleKeydown = (event: React.KeyboardEvent<HTMLLIElement>) => {
let focusElement: HTMLLIElement | null = null;

switch (event.key) {
case "Enter": {
handleTabChange(event);
break;
}
case "ArrowRight": {
const nextTab = tabRefs.indexOf(event.currentTarget) + 1;
focusElement = tabRefs[nextTab] ?? tabRefs[0]!;
break;
}
case "ArrowLeft": {
const prevTab = tabRefs.indexOf(event.currentTarget) - 1;
focusElement = tabRefs[prevTab] ?? tabRefs[tabRefs.length - 1]!;
break;
}
default:
break;
}

focusElement?.focus();
};

return (
<ul
role="tablist"
aria-orientation="horizontal"
className={clsx(
"tabs",
"openapi-tabs__code-list-container",
{
"tabs--block": block,
},
className
)}
>
{tabValues.map(({ value, label, attributes }) => (
<li
// TODO extract TabListItem
role="tab"
tabIndex={selectedValue === value ? 0 : -1}
aria-selected={selectedValue === value}
key={value}
ref={(tabControl) => tabRefs.push(tabControl)}
onKeyDown={handleKeydown}
onClick={handleTabChange}
{...attributes}
className={clsx(
"tabs__item",
"openapi-tabs__code-item",
attributes?.className as string,
{
active: selectedValue === value,
}
)}
>
<span>{label ?? value}</span>
</li>
))}
</ul>
);
}

function TabContent({
lazy,
children,
selectedValue,
}: CodeTabsProps & ReturnType<typeof useTabs>): React.JSX.Element | null {
const childTabs = (Array.isArray(children) ? children : [children]).filter(
Boolean
) as ReactElement<TabItemProps>[];
if (lazy) {
const selectedTabItem = childTabs.find(
(tabItem) => tabItem.props.value === selectedValue
);
if (!selectedTabItem) {
// fail-safe or fail-fast? not sure what's best here
return null;
}
return cloneElement(selectedTabItem, { className: "margin-top--md" });
}
return (
<div className="margin-top--md openapi-tabs__code-content">
{childTabs.map((tabItem, i) =>
cloneElement(tabItem, {
key: i,
hidden: tabItem.props.value !== selectedValue,
})
)}
</div>
);
}

function TabsComponent(props: CodeTabsProps & Props): React.JSX.Element {
const tabs = useTabs(props);
const { className } = props;

return (
<div
className={clsx("tabs-container openapi-tabs__code-container", className)}
>
<TabList {...props} {...tabs} />
<TabContent {...props} {...tabs} />
</div>
);
}

export default function CodeTabs(
props: CodeTabsProps & Props
): React.JSX.Element {
const isBrowser = useIsBrowser();
return (
<TabsComponent
// Remount tabs after hydration
// Temporary fix for https://github.com/facebook/docusaurus/issues/5653
key={String(isBrowser)}
{...props}
>
{sanitizeTabsChildren(props.children)}
</TabsComponent>
);
}

  1. Add a new _CodeTabs.scss file in the CodeTabs/ directory to define the component's styles:
_CodeTabs.scss
  :root {
--bash-background-color: transparent;
--bash-border-radius: none;
--code-tab-logo-width: 24px;
--code-tab-logo-height: 24px;
}

[data-theme="dark"] {
--bash-background-color: lightgrey;
--bash-border-radius: 20px;
}

.openapi-tabs__code-container {
margin-bottom: 1rem;

&:not(.openapi-tabs__code-container-inner) {
padding: 1rem;
background-color: var(--ifm-pre-background);
border-radius: var(--ifm-global-radius);
border: 1px solid var(--openapi-explorer-border-color);
box-shadow:
0 2px 3px hsla(222, 8%, 43%, 0.1),
0 8px 16px -10px hsla(222, 8%, 43%, 0.2);
transition: 300ms;

&:hover {
box-shadow:
0 0 0 2px rgba(38, 53, 61, 0.15),
0 2px 3px hsla(222, 8%, 43%, 0.15),
0 16px 16px -10px hsla(222, 8%, 43%, 0.2);
}
}

.openapi-tabs__code-item {
display: flex;
flex-direction: column-reverse;
flex: 0 0 80px;
align-items: center;
padding: 0.5rem 0 !important;
margin-top: 0 !important;
margin-right: 0.5rem;
border: 1px solid transparent;
transition: 300ms;

&:not(.active):hover {
border: 1px solid var(--openapi-code-tab-border-color);
}

&:hover {
background-color: transparent;
}

span {
padding-top: 0.5rem;
color: var(--ifm-font-color-secondary);
font-size: 10px;
text-transform: uppercase;
}
}
}

.openapi-tabs__code-list-container {
display: flex;
justify-content: flex-start;
padding: 0.25rem;
padding-bottom: 0.6rem;
}

.openapi-tabs__code-content {
margin-top: unset !important;
}

.openapi-explorer__code-block code {
max-height: 200px;
font-size: var(--openapi-explorer-font-size-code);
padding-top: var(--ifm-pre-padding);
}

body[class="ReactModal__Body--open"] {
.openapi-explorer__code-block code {
max-height: 600px;
}
}

.openapi-tabs__code-item--variant {
color: var(--ifm-color-secondary);

&.active {
border-color: var(--ifm-toc-border-color);
}
}

.openapi-tabs__code-item--variant > span {
padding-top: unset !important;
padding-left: 0.5rem !important;
padding-right: 0.5rem !important;
}

.openapi-tabs__code-item--sample {
color: var(--ifm-color-secondary);

&.active {
border-color: var(--ifm-toc-border-color);
}
}

.openapi-tabs__code-item--sample > span {
padding-top: unset !important;
padding-left: 0.5rem !important;
padding-right: 0.5rem !important;
}

.openapi-tabs__code-item--python {
color: var(--ifm-color-success);

&::after {
content: "";
width: var(--code-tab-logo-width);
height: var(--code-tab-logo-height);
background: url("https://raw.githubusercontent.com/devicons/devicon/master/icons/python/python-original.svg")
no-repeat;
margin-block: auto;
}

&.active {
box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-python);
border-color: var(--openapi-code-tab-border-color-python);
}
}

.openapi-tabs__code-item--go {
color: var(--ifm-color-info);

&::after {
content: "";
width: var(--code-tab-logo-width);
height: var(--code-tab-logo-height);
background: url("https://raw.githubusercontent.com/devicons/devicon/master/icons/go/go-original-wordmark.svg")
no-repeat;
margin-block: auto;
}

&.active {
box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-go);
border-color: var(--openapi-code-tab-border-color-go);
}
}

.openapi-tabs__code-item--javascript {
color: var(--ifm-color-warning);

&::after {
content: "";
width: var(--code-tab-logo-width);
height: var(--code-tab-logo-height);
background: url("https://raw.githubusercontent.com/devicons/devicon/master/icons/javascript/javascript-original.svg")
no-repeat;
margin-block: auto;
}

&.active {
box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-js);
border-color: var(--openapi-code-tab-border-color-js);
}
}

.openapi-tabs__code-item--bash {
color: var(--ifm-color-danger);

&::after {
content: "";
width: var(--code-tab-logo-width);
height: var(--code-tab-logo-height);
background: url("https://raw.githubusercontent.com/devicons/devicon/master/icons/bash/bash-plain.svg")
no-repeat;
margin-block: auto;
background-color: var(--bash-background-color);
border-radius: var(--bash-border-radius);
}

&.active {
box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-bash);
border-color: var(--ifm-color-danger);
}
}

.openapi-tabs__code-item--ruby {
color: var(--ifm-color-danger);

&::after {
content: "";
width: var(--code-tab-logo-width);
height: var(--code-tab-logo-height);
background: url("https://raw.githubusercontent.com/devicons/devicon/master/icons/ruby/ruby-plain.svg")
no-repeat;
margin-block: auto;
}

&.active {
box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-ruby);
border-color: var(--openapi-code-tab-border-color-ruby);
}
}

.openapi-tabs__code-item--csharp {
color: var(--ifm-color-gray-500);

&::after {
content: "";
width: var(--code-tab-logo-width);
height: var(--code-tab-logo-height);
background: url("https://raw.githubusercontent.com/devicons/devicon/master/icons/csharp/csharp-original.svg")
no-repeat;
margin-block: auto;
}

&.active {
box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-csharp);
border-color: var(--openapi-code-tab-border-color-csharp);
}
}

.openapi-tabs__code-item--nodejs {
color: var(--ifm-color-success);

&::after {
content: "";
width: var(--code-tab-logo-width);
height: var(--code-tab-logo-height);
background: url("https://raw.githubusercontent.com/devicons/devicon/master/icons/nodejs/nodejs-original.svg")
no-repeat;
margin-block: auto;
}

&.active {
box-shadow: 0 0 0 3px var(--opeanpi-code-tab-shadow-color-nodejs);
border-color: var(--openapi-code-tab-border-color-nodejs);
}
}

.openapi-tabs__code-item--php {
color: var(--ifm-color-gray-500);

&::after {
content: "";
width: var(--code-tab-logo-width);
height: var(--code-tab-logo-height);
background: url("https://raw.githubusercontent.com/devicons/devicon/master/icons/php/php-original.svg")
no-repeat;
margin-block: auto;
}

&.active {
box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-php);
border-color: var(--openapi-code-tab-border-color-php);
}
}

.openapi-tabs__code-item--java {
color: var(--ifm-color-warning);

&::after {
content: "";
width: var(--code-tab-logo-width);
height: var(--code-tab-logo-height);
background: url("https://raw.githubusercontent.com/devicons/devicon/master/icons/java/java-original.svg")
no-repeat;
margin-block: auto;
}

&.active {
box-shadow: 0 0 0 3px var(--openapi-code-tab-shadow-color-java);
border-color: var(--openapi-code-tab-border-color-java);
}
}

.openapi-tabs__code-item--powershell {
color: var(--ifm-color-info);

&::after {
content: "";
width: var(--code-tab-logo-width);
height: var(--code-tab-logo-height);
background: url("https://raw.githubusercontent.com/devicons/devicon/master/icons/windows8/windows8-original.svg")
no-repeat;
margin-block: auto;
}

&.active {
box-shadow: 0 0 0 3px var(--opeanpi-code-tab-shadow-color-powershell);
border-color: var(--openapi-code-tab-border-color-powershell);
}
}

@media only screen and (min-width: 768px) and (max-width: 996px) {
.openapi-tabs__code-list {
justify-content: space-around;
}
}

.ReactModal__Body--open {
overflow: hidden !important;
}

.openapi-modal--open {
background-color: rgba(0, 0, 0, 0.7) !important;
}

The CodeTabs directory should look like this:

src
└── theme
└── APIExplorer
└── CodeTabs
├── index.tsx
└── _CodeTabs.scss

6. Test the SDKs Documentation

With the openapi.json file updated to include the enhanced OpenAPI spec generated by liblab in Step 4, and after swizzling the CodeTabs and CodeSnippets components in Step 5, you're ready to review the final version of your documentation.

First, delete the existing endpoint pages and generate new ones based on the enhanced OpenAPI spec you added in Step 4. Run the following commands in your project's terminal:

npm run docusaurus clean-api-docs all
npm run docusaurus gen-api-docs all

Then, restart your documentation site by running:

npm run start

The project preview should open in your browser at http://localhost:3000/. Navigate to your documentation, select the PetStore tab, and open any endpoint page to view the code snippets from the generated SDKs now integrated into your documentation, as shown in the image below.

🎉🎉🎉   Congratulations, you've successfully implemented the enhanced OpenAPI spec in your documentation using Docusaurus!   🎉🎉🎉

Conclusion

With liblab's enhanced OpenAPI capabilities, you now have a robust API reference that includes SDK code snippets and examples, making it easier for developers to use your API. As a result, your documentation now provides developers with quick, accurate references directly from the SDKs, reinforcing both usability and accessibility.