Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
"DOMCONTENT",
"Dripicons",
"dropend",
"dropleft",
"dropright",
"dropstart",
"dropup",
"dgst",
Expand Down Expand Up @@ -108,6 +110,7 @@
"lightningcss",
"linkinator",
"llms",
"llmstxt",
"longdesc",
"Lowercased",
"markdownify",
Expand Down Expand Up @@ -186,6 +189,7 @@
"Unported",
"unstylable",
"unstyled",
"unthemed",
"Uppercased",
"urlize",
"urlquery",
Expand Down
17 changes: 17 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,23 @@ const eslintConfig = [
rules: {
'unicorn/prefer-node-protocol': 'off'
}
},

// Markdown TypeScript code blocks — parsed for syntax only, never type-checked
{
files: ['**/*.md/*.ts'],
languageOptions: {
parser: tseslint.parser
}
},

// Agent skill code blocks are illustrative docs (mirroring the docs guides),
// so allow side-effect imports like `import './styles.scss'` in bundler setups.
{
files: ['skills/**/*.md/*.js', 'skills/**/*.md/*.mjs'],
rules: {
'import/no-unassigned-import': 'off'
}
}
]

Expand Down
1 change: 1 addition & 0 deletions site/data/sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
icon_color: indigo
pages:
- title: Install
- title: AI
- title: Approach
- title: CSS variables
- title: JavaScript
Expand Down
57 changes: 57 additions & 0 deletions site/src/components/shortcodes/SkillsTable.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
import { getConfig } from '@libs/config'
import { docsPages, getDocsPageSlug } from '@libs/content'
import { getVersionedDocsPath } from '@libs/path'
import { getSkills } from '@libs/skills'

// Render the agent skills table from each `SKILL.md` frontmatter so the docs
// stay in sync with the skill files. Markup mirrors the `BsTable` shortcode.
const skills = getSkills()
const { repo } = getConfig()

function getGuide(guide: string | undefined) {
if (!guide) {
return undefined
}

const slug = guide.replace(/^\//, '')
const page = docsPages.find((docsPage) => getDocsPageSlug(docsPage.id) === slug)

if (!page) {
return undefined
}

return { href: getVersionedDocsPath(slug), label: page.data.title }
}
---

<div class="table-responsive">
<table class="table md:table-stacked">
<thead>
<tr>
<th scope="col">Skill</th>
<th scope="col">Description</th>
<th scope="col">Guide</th>
</tr>
</thead>
<tbody>
{
skills.map((skill) => {
const guide = getGuide(skill.guide)

return (
<tr>
<td>
<a href={`${repo}/tree/main/skills/${skill.name}`}>
<code class="text-nowrap">{skill.name}</code>
</a>
</td>
<td>{skill.description}</td>
<td>{guide ? <a href={guide.href}>{guide.label}</a> : '—'}</td>
</tr>
)
})
}
</tbody>
</table>
</div>
1 change: 1 addition & 0 deletions site/src/components/shortcodes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export { default as Placeholder } from './Placeholder.astro'
export { default as ResizableExample } from './ResizableExample.astro'
export { default as ScrollspyDebug } from './ScrollspyDebug.astro'
export { default as ScssDocs } from './ScssDocs.astro'
export { default as SkillsTable } from './SkillsTable.astro'
export { default as SpacingNotation } from './SpacingNotation.astro'
export { default as SpacingResponsive } from './SpacingResponsive.astro'
export { default as Swatch } from './Swatch.astro'
Expand Down
26 changes: 26 additions & 0 deletions site/src/content/docs/getting-started/ai.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
title: Using Bootstrap with AI
description: Learn about Bootstrap’s machine-readable docs and skills files for working with LLMs and agentic developer tools.
toc: true
---

Bootstrap ships resources built for large language models and AI-powered coding tools: machine-readable documentation you can feed to an LLM, and a set of agent skills that walk agents through common Bootstrap tasks step by step.

## LLM-ready docs

Our docs are available in a machine-readable format, following the [llms.txt](https://llmstxt.org/) convention.

<BsTable>
| File | Description | Link |
| --- | --- | --- |
| `llms.txt` | A curated index of every documentation page with links and descriptions. | [/llms.txt](/llms.txt) |
| `llms-full.txt` | The full text of the documentation concatenated into a single file for direct ingestion. | [/llms-full.txt](/llms-full.txt) |
</BsTable>

## Skills

Bootstrap ships [agent skills](https://github.com/twbs/bootstrap/tree/main/skills) in the `skills/` directory of our repository. Each skill is a `SKILL.md` file—a focused, step-by-step playbook that an agent can follow to complete a specific Bootstrap task, from migrating a project or setting up a new build to authoring a component and customizing our color system.

To use one, point your agent at the relevant `SKILL.md` file (or copy it into your project) and describe what you want to do. The skill’s `description` field tells the agent when it applies, and its steps keep the agent aligned with our official guidance. Always review an agent’s changes against the matching documentation.

<SkillsTable />
4 changes: 4 additions & 0 deletions site/src/content/docs/getting-started/install.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,7 @@ You can also do the old fashioned thing and download Bootstrap manually. Choose
</BsTable>

Should you require our full set of [build tools]([[docsref:/guides/contribute#tooling-setup]]), they are included for developing Bootstrap and its docs, but they’re likely unsuitable for your own purposes.

## For AI tools & LLMs

Bootstrap’s documentation is available in a machine-readable format, and we ship agent skills for AI-powered coding tools. See [AI]([[docsref:/getting-started/ai]]) for our `llms.txt` files and the full list of skills.
4 changes: 4 additions & 0 deletions site/src/content/docs/guides/npm.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ thumbnail: guides/bootstrap-npm@2x.png
**Want to skip to the end?** Download the source code and working demo for this guide from the [twbs/examples repository](https://github.com/twbs/examples/tree/sass-js/). You can also [open the example in StackBlitz](https://stackblitz.com/github/twbs/examples/tree/main/sass-js?file=index.html).
</Callout>

<Callout type="info">
**Using an AI coding agent?** Point it at our [`bootstrap-npm` skill](https://github.com/twbs/bootstrap/tree/main/skills/bootstrap-npm) to automate this setup. See [AI]([[docsref:/getting-started/ai]]) for all available skills.
</Callout>

## What is npm?

[npm](https://www.npmjs.com/) is the default package manager for Node.js. In this guide, we use npm to install Bootstrap and its dependencies, then compile Sass to CSS using command-line tools—no bundler required.
Expand Down
4 changes: 4 additions & 0 deletions site/src/content/docs/guides/parcel.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ thumbnail: guides/bootstrap-parcel@2x.png
**Want to skip to the end?** Download the source code and working demo for this guide from the [twbs/examples repository](https://github.com/twbs/examples/tree/main/parcel). You can also [open the example in StackBlitz](https://stackblitz.com/github/twbs/examples/tree/main/parcel?file=index.html) but not run it because Parcel isn’t currently supported there.
</Callout>

<Callout type="info">
**Using an AI coding agent?** Point it at our [`bootstrap-parcel` skill](https://github.com/twbs/bootstrap/tree/main/skills/bootstrap-parcel) to automate this setup. See [AI]([[docsref:/getting-started/ai]]) for all available skills.
</Callout>

## What is Parcel?

[Parcel](https://parceljs.org/) is a web application bundler designed to simplify the development process with a zero-configuration setup out of the box. It offers features found in more advanced bundlers while focusing on ease of use, making it ideal for developers seeking a quick start.
Expand Down
4 changes: 4 additions & 0 deletions site/src/content/docs/guides/vite.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ thumbnail: guides/bootstrap-vite@2x.png
**Want to skip to the end?** Download the source code and working demo for this guide from the [twbs/examples repository](https://github.com/twbs/examples/tree/main/vite). You can also [open the example in StackBlitz](https://stackblitz.com/github/twbs/examples/tree/main/vite?file=index.html) for live editing.
</Callout>

<Callout type="info">
**Using an AI coding agent?** Point it at our [`bootstrap-vite` skill](https://github.com/twbs/bootstrap/tree/main/skills/bootstrap-vite) to automate this setup. See [AI]([[docsref:/getting-started/ai]]) for all available skills.
</Callout>

## What is Vite?

[Vite](https://vite.dev/) is a modern frontend build tool designed for speed and simplicity. It provides an efficient and streamlined development experience, especially for modern JavaScript frameworks.
Expand Down
4 changes: 4 additions & 0 deletions site/src/content/docs/guides/webpack.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ thumbnail: guides/bootstrap-webpack@2x.png
**Want to skip to the end?** Download the source code and working demo for this guide from the [twbs/examples repository](https://github.com/twbs/examples/tree/main/webpack). You can also [open the example in StackBlitz](https://stackblitz.com/github/twbs/examples/tree/main/webpack?file=index.html) for live editing.
</Callout>

<Callout type="info">
**Using an AI coding agent?** Point it at our [`bootstrap-webpack` skill](https://github.com/twbs/bootstrap/tree/main/skills/bootstrap-webpack) to automate this setup. See [AI]([[docsref:/getting-started/ai]]) for all available skills.
</Callout>

## What is Webpack?

[Webpack](https://webpack.js.org/) is a JavaScript module bundler that processes modules and their dependencies to generate static assets. It simplifies managing complex web applications with multiple files and dependencies.
Expand Down
88 changes: 88 additions & 0 deletions site/src/libs/llms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { docsPages, getDocsPageSlug } from '@libs/content'
import { getData, type SidebarItem, type SidebarSubItem } from '@libs/data'
import { getVersionedDocsPath } from '@libs/path'
import { getSlug } from '@libs/utils'

export interface LlmsPage {
title: string
description: string
// The versioned docs path, e.g. `/docs/6.0/getting-started/install`.
path: string
// The raw MDX body of the page, if available.
body: string
}

export interface LlmsSection {
heading: string
pages: LlmsPage[]
}

// Resolve a sidebar entry (by its group slug and title) to a docs collection
// entry, mirroring the slug logic in `DocsSidebar.astro`. Unlike the sidebar,
// this stays resilient and returns `undefined` when there is no match instead
// of throwing, so the generated `llms.txt` files never fail a build over a
// single stray sidebar entry.
function resolvePage(groupSlug: string, title: string | undefined): LlmsPage | undefined {
if (!title) {
return undefined
}

const unversionedPageSlug = `${groupSlug}/${getSlug(title)}`
const entry = docsPages.find((page) => getDocsPageSlug(page.id) === unversionedPageSlug)

if (!entry) {
return undefined
}

return {
title: entry.data.title,
description: entry.data.description,
path: getVersionedDocsPath(unversionedPageSlug),
body: entry.body ?? ''
}
}

// Build an ordered, grouped list of docs pages from `site/data/sidebar.yml`.
// Both `/llms.txt` and `/llms-full.txt` consume this so they stay in sync with
// the site navigation.
export function getLlmsSections(): LlmsSection[] {
const sidebar = getData('sidebar')
const sections: LlmsSection[] = []

for (const group of sidebar) {
if (!group.pages) {
continue
}

const groupSlug = getSlug(group.title)
const pages: LlmsPage[] = []

for (const item of group.pages as SidebarItem[]) {
// Nested sub-group (e.g. Utilities): flatten its pages under the parent
// group heading.
if (item.group && item.pages) {
for (const subPage of item.pages as SidebarSubItem[]) {
const resolved = resolvePage(groupSlug, subPage.title)

if (resolved) {
pages.push(resolved)
}
}

continue
}

const resolved = resolvePage(groupSlug, item.title)

if (resolved) {
pages.push(resolved)
}
}

if (pages.length > 0) {
sections.push({ heading: group.title, pages })
}
}

return sections
}
89 changes: 89 additions & 0 deletions site/src/libs/skills.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import fs from 'node:fs'
import path from 'node:path'
import { load as yamlLoad } from 'js-yaml'

// The order the skills should appear in the docs. Any skill not listed here is
// appended alphabetically, so adding a new `skills/<name>/SKILL.md` shows up
// automatically without touching this file.
const skillOrder = [
'bootstrap-v4-v6-migration',
'bootstrap-v5-v6-migration',
'bootstrap-v6-install',
'bootstrap-npm',
'bootstrap-webpack',
'bootstrap-parcel',
'bootstrap-vite',
'bootstrap-component',
'bootstrap-component-js',
'bootstrap-color-system',
'bootstrap-utility-api'
]

// The skills directory lives at the repository root, resolved relative to the
// current working directory (the repo root during dev and build), mirroring how
// `config.ts` reads `./config.yml` and `data.ts` reads `./site/data`.
const skillsDirectory = './skills'

export interface Skill {
name: string
description: string
// A docs path (e.g. `/guides/npm`) linking to the matching human guide.
guide?: string
}

// Extract and parse the YAML frontmatter block from a `SKILL.md` file.
function parseFrontmatter(content: string): Record<string, unknown> {
const match = /^---\r?\n(?<frontmatter>[\s\S]*?)\r?\n---/.exec(content)

if (!match?.groups?.frontmatter) {
return {}
}

const data = yamlLoad(match.groups.frontmatter)

return typeof data === 'object' && data !== null ? (data as Record<string, unknown>) : {}
}

// Read every `skills/<name>/SKILL.md` frontmatter and return an ordered list.
// Used to generate the skills table in the docs so it never drifts from the
// actual skill files.
export function getSkills(): Skill[] {
const skills = fs
.readdirSync(skillsDirectory, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry): Skill | undefined => {
const skillPath = path.join(skillsDirectory, entry.name, 'SKILL.md')

if (!fs.existsSync(skillPath)) {
return undefined
}

const frontmatter = parseFrontmatter(fs.readFileSync(skillPath, 'utf8'))

return {
name: typeof frontmatter.name === 'string' ? frontmatter.name : entry.name,
description: typeof frontmatter.description === 'string' ? frontmatter.description : '',
guide: typeof frontmatter.guide === 'string' ? frontmatter.guide : undefined
}
})
.filter((skill): skill is Skill => skill !== undefined)

return skills.sort((a, b) => {
const indexA = skillOrder.indexOf(a.name)
const indexB = skillOrder.indexOf(b.name)

if (indexA !== -1 && indexB !== -1) {
return indexA - indexB
}

if (indexA !== -1) {
return -1
}

if (indexB !== -1) {
return 1
}

return a.name.localeCompare(b.name)
})
}
Loading