Add rendered-html gates, tidy the build and CI, clean up docs and preview (#2936)

This commit is contained in:
Paweł Kuna authored and GitHub committed 2026-09-02 21:48:43 +02:00
1 parent 1da70a787e
commit d2c127122b
126 files changed
+1183 -876

No files matched your search

+4 -4
View File
@@ -98,15 +98,15 @@ Add a test when you write a mixin whose output is easy to break silently — the
## 8. Gates
```bash
pnpm run lint:scss # stylelint (twbs config; prettier owns formatting)
pnpm --filter @tabler/core css-lint # + find-unused-sass-variables
pnpm --filter @tabler/core lint:scss # stylelint (twbs config; prettier owns formatting)
pnpm run lint-scss # + find-unused-sass-variables (`lint:scss:vars` in core)
pnpm run generate-tokens:check # shared/lib/tokens.ts must match the Sass maps
pnpm run lint-prettier # formatting
pnpm run bundlewatch # size budgets (tabler.css 80 kB, tabler.min.css 75 kB)
```
- A new entry in `$theme-colors`, `$avatar-sizes` and friends must be regenerated into `shared/lib/tokens.ts` with `pnpm run generate-tokens` — the check gate fails otherwise.
- An unused Sass variable fails `css-lint`; delete it or use it.
- An unused Sass variable fails `lint-scss`; delete it or use it.
- Growth past a bundlewatch limit is a decision, not an accident: raise the number in `core/package.json` deliberately and say so in the PR.
## 9. Checklist
@@ -117,5 +117,5 @@ pnpm run bundlewatch # size budgets (tabler.css 80 kB, tab
- [ ] Dark mode via `light-dark()` or all three dark selectors, behind `$enable-dark-mode`
- [ ] Logical properties, or `--dir` + `/* rtl:ignore */`; no hand-written RTL
- [ ] sass-true test for a mixin that can break silently
- [ ] `lint:scss`, `css-lint`, `generate-tokens:check`, `lint-prettier`, `test:scss` clean
- [ ] `lint-scss`, `generate-tokens:check`, `lint-prettier`, `test:scss` clean
- [ ] Docs page and class table updated (`write-docs`, `class-reference`), changeset written
+7 -3
View File
@@ -13,7 +13,10 @@
// --banner adds the license comment before any source map is generated, so
// the maps account for its lines (#2766).
//
// Usage: tsx ../.build/build-css.ts <scssDir> <outDir> [--rtl] [--minify] [--banner]
// --no-prefix skips the --tblr- pass, for a stylesheet that inlines vendor css
// whose variables are set at runtime (docs/scss/docs.scss).
//
// Usage: tsx ../.build/build-css.ts <scssDir> <outDir> [--rtl] [--minify] [--banner] [--no-prefix]
// (cwd = the package)
/// <reference path="./modules.d.ts" />
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
@@ -32,12 +35,13 @@ const args = process.argv.slice(2)
const flags = args.filter((arg) => arg.startsWith('--'))
const [scssDir, outDir] = args.filter((arg) => !arg.startsWith('--'))
if (!scssDir || !outDir) {
console.error('usage: tsx build-css.ts <scssDir> <outDir> [--rtl] [--minify] [--banner]')
console.error('usage: tsx build-css.ts <scssDir> <outDir> [--rtl] [--minify] [--banner] [--no-prefix]')
process.exit(1)
}
const withRtl = flags.includes('--rtl')
const withMinify = flags.includes('--minify')
const withBanner = flags.includes('--banner')
const withPrefix = !flags.includes('--no-prefix')
const mapOptions = { inline: false, annotation: true, sourcesContent: true }
@@ -66,7 +70,7 @@ async function compile(entry: string): Promise<{ outFile: string; result: Result
// postcss maps generated positions back to *its own input*, so renaming
// inside the map-generating pass would leave every mapping — and the
// embedded sourcesContent — describing css that no longer exists on disk.
const { css: prefixed } = await postcss([inlineValueComments, prefixCustomProperties({ prefix: cssVarPrefix, ignore: cssVarIgnore })]).process(input, { from: outFile, to: outFile, map: false })
const { css: prefixed } = withPrefix ? await postcss([inlineValueComments, prefixCustomProperties({ prefix: cssVarPrefix, ignore: cssVarIgnore })]).process(input, { from: outFile, to: outFile, map: false }) : { css: input }
const result = await postcss([autoprefixer({ cascade: false })]).process(prefixed, {
from: outFile,
to: outFile,
+4 -2
View File
@@ -88,7 +88,7 @@ const headingSlugsOf = (file: string): Set<string> => {
const slugger = new GithubSlugger()
slugs = new Set<string>()
if (file.endsWith('.mdx')) {
for (const line of stripDemosAndCode(body(file)).split('\n')) {
for (const line of stripFences(body(file)).split('\n')) {
const heading = line.match(/^#{1,6}\s+(.*)$/)
if (heading) {
const text = (heading[1] ?? '').replace(/[`*_]|\[([^\]]*)\]\([^)]*\)/g, '$1').trim()
@@ -111,7 +111,9 @@ const frontmatter = (file: string): string => {
return source.startsWith('---') ? (source.split(/^---$/m, 3)[1] ?? '') : ''
}
const stripCode = (text: string): string => text.replace(/^```[\s\S]*?^```/gm, '').replace(/`[^`\n]*`/g, '')
// Fenced blocks only: heading text keeps its inline code, which the slugger sees too.
const stripFences = (text: string): string => text.replace(/^```[\s\S]*?^```/gm, '')
const stripCode = (text: string): string => stripFences(text).replace(/`[^`\n]*`/g, '')
const stripDemosAndCode = (text: string): string =>
stripCode(text)
+225
View File
@@ -0,0 +1,225 @@
// Renders every page of a built site (or a running dev server) in Chromium and
// fails on the mistakes a static check cannot see: dead internal links, script
// errors, duplicate ids, controls that point at ids that do not exist, nested
// interactive elements. Images without alt and controls without an accessible
// name are reported as warnings.
//
// Pages come from the site's sitemap.xml. A directory target (preview/dist,
// docs/dist) is served locally; an http(s) target is crawled as is.
//
// Run: pnpm run check-html preview/dist docs/dist
// pnpm run check-html http://localhost:3000
import { createReadStream, existsSync, readFileSync, statSync } from 'node:fs'
import { createServer, type Server } from 'node:http'
import { extname, join, resolve } from 'node:path'
import { chromium, type Browser, type Page } from 'playwright'
const MIME: Record<string, string> = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'text/javascript',
'.mjs': 'text/javascript',
'.json': 'application/json',
'.svg': 'image/svg+xml',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.webp': 'image/webp',
'.gif': 'image/gif',
'.ico': 'image/x-icon',
'.woff2': 'font/woff2',
'.woff': 'font/woff',
'.ttf': 'font/ttf',
'.xml': 'application/xml',
'.txt': 'text/plain',
'.md': 'text/markdown',
'.map': 'application/json',
'.mp4': 'video/mp4',
'.webm': 'video/webm',
'.mp3': 'audio/mpeg',
}
// Ids generated by libraries at runtime; duplicates there are theirs, not ours.
const VENDOR_ID_PREFIXES = ['apexcharts-', 'jvm-', 'SvgjsSvg', 'SvgjsLinearGradient']
// Console noise from third-party demos (WebGL driver messages, Google Maps without a key, plyr).
const CONSOLE_IGNORE = [/GL Driver Message/, /Google Maps JavaScript API/, /allowfullscreen/, /web-share/, /No available adapters/, /\[vite\]/, /astro/i]
type PageReport = { url: string; errors: string[]; warnings: string[] }
const resolveFile = (root: string, urlPath: string): string | null => {
const clean = decodeURIComponent(urlPath.split('?')[0]!.split('#')[0]!)
// `/x/` and `/x/index.html` also resolve to x.html: Astro's file format
// collapses x/index.astro to x.html and the hosting serves it under both.
const collapsed = clean.replace(/\/(index\.html)?$/, '')
const candidates = [join(root, clean), join(root, `${clean}.html`), join(root, clean, 'index.html'), join(root, `${collapsed}.html`)]
for (const c of candidates) {
if (existsSync(c) && statSync(c).isFile()) return c
}
return null
}
const serve = (root: string): Promise<{ server: Server; base: string }> =>
new Promise((done) => {
const server = createServer((req, res) => {
const file = resolveFile(root, req.url ?? '/')
if (!file) {
res.writeHead(404).end('not found')
return
}
res.writeHead(200, { 'content-type': MIME[extname(file)] ?? 'application/octet-stream' })
if (req.method === 'HEAD') {
res.end()
return
}
createReadStream(file).pipe(res)
})
server.listen(0, '127.0.0.1', () => {
const address = server.address()
const port = typeof address === 'object' && address ? address.port : 0
done({ server, base: `http://127.0.0.1:${port}` })
})
})
const sitemapPaths = async (base: string, dir?: string): Promise<string[]> => {
const xml = dir ? readFileSync(join(dir, 'sitemap.xml'), 'utf8') : await (await fetch(`${base}/sitemap.xml`)).text()
return [...xml.matchAll(/<loc>([^<]+)<\/loc>/g)].map((m) => m[1]!.replace(/^https?:\/\/[^/]+/, '')).filter((p) => p.startsWith('/'))
}
// Runs in the page as plain JavaScript (a string, so no bundler helpers such as
// esbuild's __name leak into the browser): everything the DOM can tell us.
const INSPECT = `(() => {
const accName = (el) => {
const labelledBy = el.getAttribute('aria-labelledby')
if (labelledBy) {
const text = labelledBy.split(/\\s+/).map((id) => (document.getElementById(id) || {}).textContent || '').join(' ').trim()
if (text) return text
}
const own = el.getAttribute('aria-label') || el.getAttribute('title') || (el.innerText || '').trim() || (el.textContent || '').trim()
if (own) return own
return [...el.querySelectorAll('img[alt], [role="img"][aria-label], [aria-label], svg title')].map((e) => e.getAttribute('alt') || e.getAttribute('aria-label') || e.textContent || '').join(' ').trim()
}
const hidden = (el) => !!el.closest('[aria-hidden="true"]')
const ids = new Map()
for (const el of document.querySelectorAll('[id]')) ids.set(el.id, (ids.get(el.id) || 0) + 1)
const duplicateIds = [...ids].filter(([, n]) => n > 1).map(([id]) => id)
const missingTargets = []
const check = (attr, value) => {
if (!value) return
for (const raw of value.split(/\\s+/)) {
const id = raw.replace(/^#/, '')
if (!id) continue
if (!document.getElementById(id)) missingTargets.push(attr + '="' + raw + '"')
}
}
for (const el of document.querySelectorAll('[data-bs-target^="#"]')) check('data-bs-target', el.getAttribute('data-bs-target'))
for (const el of document.querySelectorAll('[data-bs-toggle][href^="#"]')) check('href', el.getAttribute('href'))
for (const el of document.querySelectorAll('[aria-controls]')) check('aria-controls', el.getAttribute('aria-controls'))
for (const el of document.querySelectorAll('[aria-labelledby]')) check('aria-labelledby', el.getAttribute('aria-labelledby'))
for (const el of document.querySelectorAll('[aria-describedby]')) check('aria-describedby', el.getAttribute('aria-describedby'))
for (const el of document.querySelectorAll('label[for]')) check('for', el.getAttribute('for'))
const short = (e) => e.outerHTML.slice(0, 80).replace(/\\s+/g, ' ')
const nested = [...document.querySelectorAll('a a, a button, button a, button button, label label, a [role="button"], button [role="button"]')].map(short)
const imgNoAlt = [...document.querySelectorAll('img:not([alt])')].filter((e) => !hidden(e)).map((e) => e.getAttribute('src') || '')
const unnamed = [...document.querySelectorAll('a[href], button')].filter((e) => !hidden(e) && !accName(e)).map(short)
const links = [...document.querySelectorAll('a[href]')].map((a) => a.getAttribute('href')).filter((h) => !h.startsWith('#') && !/^(https?:|mailto:|tel:|javascript:|data:)/.test(h))
return { duplicateIds: [...new Set(duplicateIds)], missingTargets: [...new Set(missingTargets)], nested, imgNoAlt, unnamed, links }
})()`
type Inspection = { duplicateIds: string[]; missingTargets: string[]; nested: string[]; imgNoAlt: string[]; unnamed: string[]; links: string[] }
const crawl = async (browser: Browser, base: string, paths: string[]) => {
const reports: PageReport[] = []
const links = new Map<string, string>()
const queue = [...paths]
const context = await browser.newContext({ viewport: { width: 1280, height: 900 } })
const worker = async () => {
while (queue.length) {
const path = queue.shift()!
const page: Page = await context.newPage()
const report: PageReport = { url: path, errors: [], warnings: [] }
page.on('pageerror', (e) => report.errors.push(`script error: ${String(e).slice(0, 160)}`))
page.on('console', (m) => {
if (m.type() !== 'error') return
const text = m.text()
if (!CONSOLE_IGNORE.some((re) => re.test(text))) report.warnings.push(`console: ${text.slice(0, 160)}`)
})
page.on('response', (r) => {
// /_vercel/ is Vercel's own analytics script, which only exists once deployed there.
if (r.status() >= 400 && r.url().startsWith(base) && !r.url().includes('/_vercel/')) report.errors.push(`${r.status()} ${r.url().slice(base.length)}`)
})
try {
await page.goto(base + path, { waitUntil: 'load', timeout: 60_000 })
await page.waitForTimeout(500)
const r = (await page.evaluate(INSPECT)) as Inspection
for (const id of r.duplicateIds) if (!VENDOR_ID_PREFIXES.some((p) => id.startsWith(p))) report.errors.push(`duplicate id "${id}"`)
for (const t of r.missingTargets) report.errors.push(`no element for ${t}`)
for (const n of r.nested) report.errors.push(`nested interactive: ${n}`)
for (const src of r.imgNoAlt) report.warnings.push(`img without alt: ${src}`)
for (const html of r.unnamed) report.warnings.push(`no accessible name: ${html}`)
for (const href of r.links) {
const key = href.split(/[?#]/)[0]!
if (!links.has(key)) links.set(key, path)
}
} catch (e) {
report.errors.push(`navigation: ${String(e).slice(0, 160)}`)
}
reports.push(report)
await page.close()
}
}
await Promise.all(Array.from({ length: 4 }, worker))
await context.close()
for (const [href, from] of links) {
const url = href.startsWith('/') ? base + href : new URL(href, base + from).href
try {
let res = await fetch(url, { method: 'HEAD', redirect: 'manual' })
if (res.status === 405 || res.status === 501) res = await fetch(url, { method: 'GET', redirect: 'manual' })
if (res.status >= 400) reports.push({ url: from, errors: [`dead link ${href} (${res.status})`], warnings: [] })
} catch (e) {
reports.push({ url: from, errors: [`dead link ${href} (${String(e).slice(0, 60)})`], warnings: [] })
}
}
return reports
}
const main = async () => {
const targets = process.argv.slice(2).filter((a) => !a.startsWith('--'))
const quiet = process.argv.includes('--quiet')
if (targets.length === 0) {
console.error('Usage: tsx .build/check-html.ts <dist-dir | http://host:port> ...')
process.exit(2)
}
const browser = await chromium.launch()
let errorCount = 0
let warningCount = 0
for (const target of targets) {
let base = target
let server: Server | undefined
let dir: string | undefined
if (!/^https?:/.test(target)) {
dir = resolve(target)
;({ server, base } = await serve(dir))
}
const paths = await sitemapPaths(base, dir)
const reports = await crawl(browser, base, paths)
const errors = reports.filter((r) => r.errors.length)
const warnings = reports.filter((r) => r.warnings.length)
errorCount += errors.reduce((n, r) => n + r.errors.length, 0)
warningCount += warnings.reduce((n, r) => n + r.warnings.length, 0)
console.log(`${target}: ${paths.length} pages, ${errors.reduce((n, r) => n + r.errors.length, 0)} errors, ${warnings.reduce((n, r) => n + r.warnings.length, 0)} warnings`)
for (const r of errors) for (const e of r.errors) console.log(` ✗ ${r.url}: ${e}`)
if (!quiet) for (const r of warnings) for (const w of r.warnings) console.log(` · ${r.url}: ${w}`)
server?.close()
}
await browser.close()
if (errorCount > 0) {
console.error(`\n${errorCount} error(s), ${warningCount} warning(s)`)
process.exit(1)
}
console.log(`\nOK — no errors, ${warningCount} warning(s)`)
}
main().catch((e) => {
console.error(e)
process.exit(1)
})
+111
View File
@@ -0,0 +1,111 @@
// Fails when a rendered page uses a class that no shipped stylesheet defines.
//
// A typo in a class name (`ml-auto`, `progress-xs`) renders as nothing: no
// error, no warning, just markup that silently does not do what it says — and
// it ends up in the html people copy from the preview. This scans the class
// attributes of every page from a site's sitemap and looks each class up in
// the css of the given directories.
//
// Classes that exist only as JavaScript or demo hooks are listed in
// markup-classes-baseline.txt. Anything not on that list fails the check; so
// does a name on the list that no longer appears, which keeps the file from
// going stale — delete the line when you drop the hook.
//
// Run: pnpm run check-markup-classes --html preview/dist --css core/dist/css --css core/dist/libs
// Targets can be a built directory or a running server (http://localhost:3000).
import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'
import { join, resolve } from 'node:path'
const BASELINE = resolve(__dirname, 'markup-classes-baseline.txt')
const args = process.argv.slice(2)
const htmlTargets: string[] = []
const cssDirs: string[] = []
let updateBaseline = false
for (let i = 0; i < args.length; i++) {
if (args[i] === '--html') htmlTargets.push(args[++i]!)
else if (args[i] === '--css') cssDirs.push(args[++i]!)
else if (args[i] === '--update-baseline') updateBaseline = true
}
if (htmlTargets.length === 0 || cssDirs.length === 0) {
console.error('Usage: tsx .build/check-markup-classes.ts --html <dist-dir|url> [--html …] --css <dir> [--css …] [--update-baseline]')
process.exit(2)
}
const walk = (dir: string, ext: string): string[] =>
readdirSync(dir).flatMap((name) => {
const path = join(dir, name)
return statSync(path).isDirectory() ? walk(path, ext) : path.endsWith(ext) ? [path] : []
})
const defined = new Set<string>()
for (const dir of cssDirs) {
for (const file of walk(resolve(dir), '.css')) {
for (const m of readFileSync(file, 'utf8').matchAll(/\.(-?[_a-zA-Z][\w-]*)/g)) defined.add(m[1]!)
}
}
const pagesOf = async (target: string): Promise<{ path: string; html: string }[]> => {
if (/^https?:/.test(target)) {
const xml = await (await fetch(`${target}/sitemap.xml`)).text()
const paths = [...xml.matchAll(/<loc>([^<]+)<\/loc>/g)].map((m) => m[1]!.replace(/^https?:\/\/[^/]+/, ''))
const pages = []
for (const path of paths) pages.push({ path, html: await (await fetch(target + path)).text() })
return pages
}
const root = resolve(target)
const xml = readFileSync(join(root, 'sitemap.xml'), 'utf8')
return [...xml.matchAll(/<loc>([^<]+)<\/loc>/g)].map((m) => {
const path = m[1]!.replace(/^https?:\/\/[^/]+/, '')
const file = [join(root, path), join(root, `${path}.html`), join(root, path, 'index.html')].find((f) => existsSync(f) && statSync(f).isFile())
return { path, html: file ? readFileSync(file, 'utf8') : '' }
})
}
const main = async () => {
const used = new Map<string, Set<string>>()
let pageCount = 0
for (const target of htmlTargets) {
for (const { path, html } of await pagesOf(target)) {
pageCount++
// Page-level <style> blocks define classes too (demo pages style their own markup).
const inline = new Set<string>()
for (const block of html.matchAll(/<style[^>]*>([\s\S]*?)<\/style>/g)) for (const m of block[1]!.matchAll(/\.(-?[_a-zA-Z][\w-]*)/g)) inline.add(m[1]!)
for (const m of html.matchAll(/class="([^"]*)"/g)) {
for (const cls of m[1]!.split(/\s+/).filter(Boolean)) {
if (defined.has(cls) || inline.has(cls)) continue
if (!used.has(cls)) used.set(cls, new Set())
used.get(cls)!.add(`${target}${path}`)
}
}
}
}
const baseline = new Set(
existsSync(BASELINE)
? readFileSync(BASELINE, 'utf8')
.split('\n')
.map((l) => l.trim())
.filter((l) => l && !l.startsWith('#'))
: [],
)
if (updateBaseline) {
const header = '# Classes used in the rendered html that no stylesheet defines on purpose:\n# JavaScript hooks and demo-only markers. One per line; delete a line when the hook goes.\n'
writeFileSync(BASELINE, header + [...used.keys()].sort().join('\n') + '\n')
console.log(`wrote ${used.size} classes to ${BASELINE}`)
return
}
const unknown = [...used].filter(([cls]) => !baseline.has(cls)).sort((a, b) => b[1].size - a[1].size)
const stale = [...baseline].filter((cls) => !used.has(cls))
for (const [cls, pages] of unknown) console.log(`✗ .${cls} — not in any stylesheet, used on ${pages.size} page(s), e.g. ${[...pages][0]}`)
for (const cls of stale) console.log(`✗ .${cls} — listed in ${BASELINE} but no longer used (delete the line)`)
if (unknown.length || stale.length) {
console.error(`\n${unknown.length} unknown class(es), ${stale.length} stale baseline entr(ies) — ${pageCount} pages, ${defined.size} defined classes`)
process.exit(1)
}
console.log(`OK — ${pageCount} pages checked against ${defined.size} classes, ${baseline.size} known hooks`)
}
main().catch((e) => {
console.error(e)
process.exit(1)
})
+3 -2
View File
@@ -33,8 +33,9 @@ for (const file of sync(join(pagesDir, '**', '*.astro'))) {
routes.set((url === '/' ? 'index' : url.slice(1)).replaceAll('/', '_') + '.html', url)
}
// The footer stamps the render time into every page — meaningless for diffing.
const normalize = (html: string) => html.replace(/Generated \d{4}[^<]*/g, 'Generated')
// The footer stamps the render time into every page, and the dev server injects
// its own client script — neither says anything about the rendered markup.
const normalize = (html: string) => html.replace(/Generated \d{4}[^<]*/g, 'Generated').replace(/[ \t]*<script type="module" src="\/@vite\/client"><\/script>\n?/g, '')
const fetchPage = async (url: string): Promise<string> => {
const response = await fetch(baseUrl + url)
+55
View File
@@ -0,0 +1,55 @@
# Classes used in the rendered html that no stylesheet defines on purpose:
# JavaScript hooks and demo-only markers. One per line; delete a line when the hook goes.
callout-note
checkbox-wrapper-mail
datagrid-content
datagrid-item
datatable
docs-classnames
docs-page-lead
docs-search
docs-theme-toggle
dropzone-msg-desc
dropzone-msg-title
email-action-icons
email-action-icons-item
email-border
email-content
email-date
email-list
email-sender-info
email-subject
email-title
fallback
file-offcanvas
github-dark
icon-tabler
icon-tabler-pencil
icon-tabler-settings
icon-tabler-star
icon-toggle-hide
icon-toggle-show
icons-tabler-outline
js-select-color
js-select-illustration
js-select-skin-color
line
list
settings
slide
sort-category
sort-city
sort-date
sort-name
sort-progress
sort-quantity
sort-score
sort-status
sort-tags
sort-type
star-toggle
table-tbody
tblr-illustrations-computer-fix-a
tblr-illustrations-computer-fix-b
tblr-illustrations-computer-fix-c
tblr-illustrations-computer-fix-d
+6 -4
View File
@@ -77,10 +77,12 @@ async function formatExamples(source: string): Promise<string> {
return replaceAsync(source, /(<Example\b[^>]*>\n)([\s\S]*?)(\n<\/Example>)/g, async (_m: string, open: string, inner: string, close: string) => {
if (inner.includes('{')) return _m
// Whitespace sensitivity is off: MDX drops whitespace between sibling JSX
// elements anyway, and with the default `css` sensitivity prettier glues
// inline siblings together (`</span><span`) and wraps inside their tags.
const formatted = glueTextLines((await formatHTML(inner, { embeddedLanguageFormatting: 'off', htmlWhitespaceSensitivity: 'ignore' })).replace(/^\s*[\r\n]/gm, '').trim())
// Whitespace sensitivity follows css display: an inline element keeps its
// text exactly (`ignore` would pad `<span>@</span>JohnDoe` with spaces the
// reader can see), and a block element still gets one child per line. A
// long inline line wraps inside its tags (`</span\n><span`); the wide
// print width keeps that to the rare very long example.
const formatted = glueTextLines((await formatHTML(inner, { embeddedLanguageFormatting: 'off', htmlWhitespaceSensitivity: 'css', printWidth: 160 })).replace(/^\s*[\r\n]/gm, '').trim())
if (!formatted) return _m
if (!(await compilesAsMdx(open + formatted + close))) {
+17
View File
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"types": ["node"],
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"allowJs": true
},
"include": ["./*.ts", "./*.mts", "./*.d.ts", "../core/.build/*.ts", "../core/.build/*.mts", "../preview/.build/*.mts", "../screenshots/.build/*.ts"]
}
-1
View File
@@ -50,7 +50,6 @@ export function createViteConfig({ entry, name, fileName, formats, outDir, banne
target: 'es2015',
tsconfigRaw: {
compilerOptions: {
module: 'ES2020',
target: 'ES2015',
},
},
+5
View File
@@ -0,0 +1,5 @@
---
"@tabler/core": minor
---
Added TypeScript declarations to the npm package, so `import { Modal } from "@tabler/core"` is typed.
+5
View File
@@ -0,0 +1,5 @@
---
"@tabler/docs": minor
---
Added a Background blur page, gray utilities on the colors page and a language selector section on the navbars page.
+5
View File
@@ -0,0 +1,5 @@
---
"@tabler/docs": patch
---
Fixed duplicate ids and missing targets in the modal, offcanvas, page headers and page layouts examples.
+5
View File
@@ -0,0 +1,5 @@
---
"@tabler/docs": patch
---
Removed the Inter font import from `DocsLayout.astro`, the docs now use the Geist font that ships with Tabler.
@@ -0,0 +1,5 @@
---
"@tabler/core": minor
---
Added `.bg-gradient-{from,via,to}-transparent` and `-inverted` gradient stops, which the gradient docs already described.
@@ -0,0 +1,5 @@
---
"@tabler/preview": patch
---
Removed classes that no stylesheet defines from demo markup and added the onboarding and settings plan pages to the menu.
@@ -0,0 +1,5 @@
---
"@tabler/core": patch
---
Fixed `tabler-theme.js` replacing a server-rendered `data-bs-theme` when the visitor has no stored choice.
+26
View File
@@ -0,0 +1,26 @@
name: Set up the workspace
description: Restore the turbo cache, install pnpm and Node.js, install the dependencies
runs:
using: composite
steps:
- name: Cache turbo build setup
uses: actions/cache@v6
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-
- name: Install PNPM
uses: pnpm/action-setup@v6
- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- name: Install pnpm dependencies
run: pnpm install
shell: bash
+3 -1
View File
@@ -1,7 +1,9 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
directories:
- "/"
- "/.github/actions/setup"
schedule:
interval: "weekly"
labels:
+14 -21
View File
@@ -2,6 +2,9 @@ name: Build
on:
pull_request: null
push:
branches:
- dev
permissions:
contents: read
@@ -13,27 +16,17 @@ jobs:
- name: Clone repository
uses: actions/checkout@v7
- name: Cache turbo build setup
uses: actions/cache@v6
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-
- name: Install PNPM
uses: pnpm/action-setup@v6
- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- run: node --version
- name: Install pnpm dependencies
run: pnpm install
- name: Set up the workspace
uses: ./.github/actions/setup
- name: Build
run: pnpm run build
- name: Install Playwright
run: pnpm exec playwright install --with-deps chromium
- name: Check rendered html
run: pnpm run check-html preview/dist docs/dist --quiet
- name: Check markup classes
run: pnpm run check-markup-classes --html preview/dist --html docs/dist --css preview/dist/dist/css --css preview/dist/dist/libs --css preview/dist/preview/css --css docs/dist/css --css docs/dist/preview/css
+2 -25
View File
@@ -18,31 +18,8 @@ jobs:
- name: Clone repository
uses: actions/checkout@v7
- name: Cache turbo build setup
uses: actions/cache@v6
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-
- name: Install PNPM
uses: pnpm/action-setup@v6
- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- name: Set up Bundler
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.2
bundler-cache: true
- name: Install pnpm dependencies
run: pnpm install --no-frozen-lockfile
- name: Set up the workspace
uses: ./.github/actions/setup
- name: Run bundlewatch
run: pnpm run bundlewatch
+2 -11
View File
@@ -17,17 +17,8 @@ jobs:
- name: Clone repository
uses: actions/checkout@v7
- name: Install PNPM
uses: pnpm/action-setup@v6
- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Set up the workspace
uses: ./.github/actions/setup
- name: Install Playwright Chromium
run: pnpm exec playwright install --with-deps chromium
+5 -11
View File
@@ -2,6 +2,9 @@ name: Lint
on:
pull_request: null
push:
branches:
- dev
permissions:
contents: read
@@ -13,17 +16,8 @@ jobs:
- name: Clone repository
uses: actions/checkout@v7
- name: Install PNPM
uses: pnpm/action-setup@v6
- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- name: Install pnpm dependencies
run: pnpm install
- name: Set up the workspace
uses: ./.github/actions/setup
- name: Lint
run: pnpm run lint
+13 -11
View File
@@ -24,25 +24,17 @@ jobs:
- name: Checkout Repo
uses: actions/checkout@v7
- name: Install PNPM
uses: pnpm/action-setup@v6
- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- name: Set up the workspace
uses: ./.github/actions/setup
- name: Update npm # trusted publishing requires npm >= 11.5.1
run: npm install -g npm@latest
- name: Install Dependencies
run: pnpm install
- name: Build
run: pnpm run build
- name: Create release Pull Request or publish to NPM
id: changesets
uses: changesets/action@v2
with:
version-script: pnpm run version
@@ -51,3 +43,13 @@ jobs:
pr-title: "chore: update versions"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# `pnpm run build` above zipped preview/dist into packages-zip/; attach it to
# the GitHub release that changesets/action just created for @tabler/core.
- name: Attach the preview zip to the GitHub release
if: steps.changesets.outputs.published == 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
version=$(node -p "require('./core/package.json').version")
gh release upload "@tabler/core@${version}" "packages-zip/tabler-${version}.zip" --clobber
+2 -11
View File
@@ -17,17 +17,8 @@ jobs:
- name: Clone repository
uses: actions/checkout@v7
- name: Install PNPM
uses: pnpm/action-setup@v6
- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Set up the workspace
uses: ./.github/actions/setup
- name: Run SCSS tests
run: pnpm --filter @tabler/core test:scss
+5 -19
View File
@@ -17,26 +17,12 @@ jobs:
- name: Clone repository
uses: actions/checkout@v7
- name: Cache turbo build setup
uses: actions/cache@v6
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-
- name: Install PNPM
uses: pnpm/action-setup@v6
- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- name: Install pnpm dependencies
run: pnpm install
- name: Set up the workspace
uses: ./.github/actions/setup
- name: Run type-check
run: pnpm run type-check
- name: Type-check the build scripts
run: pnpm run type-check:build
-1
View File
@@ -34,7 +34,6 @@ node_modules/
.turbo
package-lock.json
/core/demo/
dist/
packages-zip/
.env
+5 -9
View File
@@ -1,8 +1,7 @@
#!/usr/bin/env node
import { existsSync, mkdirSync } from 'node:fs'
import { cpSync, existsSync, mkdirSync, rmSync } from 'node:fs'
import { globSync } from 'glob'
import { emptyDirSync, copySync } from 'fs-extra/esm'
import libs from '../libs.json' with { type: 'json' }
import { fileURLToPath } from 'node:url'
import { join, dirname } from 'node:path'
@@ -23,7 +22,8 @@ interface Libs {
const libsData = libs as Libs
emptyDirSync(join(__dirname, '..', 'dist/libs'))
rmSync(join(__dirname, '..', 'dist/libs'), { recursive: true, force: true })
mkdirSync(join(__dirname, '..', 'dist/libs'), { recursive: true })
for (const name in libsData) {
const { npm, js, css, extra } = libsData[name]
@@ -49,9 +49,7 @@ for (const name in libsData) {
if (files) {
for (const file of files) {
copySync(join(pkgDir, file), join(to, file), {
dereference: true,
})
cpSync(join(pkgDir, file), join(to, file), { recursive: true, dereference: true })
}
}
@@ -64,9 +62,7 @@ for (const name in libsData) {
}
for (const file of licenses) {
copySync(join(pkgDir, file), join(to, file), {
dereference: true,
})
cpSync(join(pkgDir, file), join(to, file), { recursive: true, dereference: true })
}
console.log(`Successfully copied ${npm}`)
+3 -8
View File
@@ -1,7 +1,6 @@
#!/usr/bin/env node
import { existsSync, mkdirSync } from 'node:fs'
import { copySync } from 'fs-extra/esm'
import { cpSync, existsSync, mkdirSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { join, dirname } from 'node:path'
@@ -22,9 +21,7 @@ if (existsSync(monoFrom)) {
mkdirSync(monoTo, { recursive: true })
}
copySync(monoFrom, monoTo, {
dereference: true,
})
cpSync(monoFrom, monoTo, { recursive: true, dereference: true })
console.log(`Successfully copied geist-mono fonts`)
} else {
@@ -39,9 +36,7 @@ if (existsSync(sansFrom)) {
mkdirSync(sansTo, { recursive: true })
}
copySync(sansFrom, sansTo, {
dereference: true,
})
cpSync(sansFrom, sansTo, { recursive: true, dereference: true })
console.log(`Successfully copied geist-sans fonts`)
} else {
+3 -1
View File
@@ -35,8 +35,10 @@ for (const key in themeConfig) {
localStorage.setItem('tabler-' + key, param)
selectedValue = param
} else {
// A stored choice wins; otherwise a server-rendered attribute is the starting value.
const storedTheme = localStorage.getItem('tabler-' + key)
selectedValue = storedTheme ? storedTheme : themeConfig[key as keyof ThemeConfig]
const serverValue = document.documentElement.getAttribute('data-bs-' + key)
selectedValue = storedTheme ?? serverValue ?? themeConfig[key as keyof ThemeConfig]
}
if (key === 'theme' && selectedValue === 'auto') {
+8 -11
View File
@@ -8,20 +8,21 @@
"dev-prepare": "pnpm run clean && pnpm run copy && concurrently \"pnpm run css-build\" \"pnpm run js-build\"",
"build": "pnpm run clean && pnpm run build-assets && pnpm run copy",
"build-assets": "concurrently \"pnpm run css\" \"pnpm run js\"",
"clean": "shx rm -rf dist demo",
"clean": "shx rm -rf dist",
"css": "tsx ../.build/build-css.ts scss dist/css --rtl --minify --banner",
"css-build": "tsx ../.build/build-css.ts scss dist/css --rtl",
"css-lint": "pnpm run lint:scss && pnpm run css-lint-variables",
"lint": "pnpm run lint:scss && pnpm run lint:scss:vars",
"lint:scss": "stylelint \"scss/**/*.scss\" --config ../stylelint.config.mjs",
"lint:scss:fix": "pnpm run lint:scss --fix",
"css-lint-variables": "find-unused-sass-variables scss/",
"js": "pnpm run js-build && pnpm run js-build-min",
"lint:scss:vars": "find-unused-sass-variables scss/",
"js": "pnpm run js-build && pnpm run js-build-min && pnpm run js-types",
"js-types": "tsc -p tsconfig.types.json",
"js-build": "concurrently \"pnpm run js-build-standalone\" \"pnpm run js-build-theme\"",
"js-build-theme": "cross-env BASE_NAME=tabler-theme vite build --config .build/vite.config.mts",
"js-build-standalone": "cross-env BASE_NAME=tabler vite build --config .build/vite.config.mts",
"js-build-min": "concurrently \"pnpm run js-build-min-standalone\" \"pnpm run js-build-min-theme\"",
"js-build-min-standalone": "concurrently \"terser dist/js/tabler.js --compress --mangle --comments '/@license|@preserve|^!/' --source-map \\\"content=dist/js/tabler.js.map,filename=dist/js/tabler.min.js.map,url=tabler.min.js.map\\\" -o dist/js/tabler.min.js\" \"terser dist/js/tabler.esm.js --module --compress --mangle --comments '/@license|@preserve|^!/' --source-map \\\"content=dist/js/tabler.esm.js.map,filename=dist/js/tabler.esm.min.js.map,url=tabler.esm.min.js.map\\\" -o dist/js/tabler.esm.min.js\"",
"js-build-min-theme": "concurrently \"terser dist/js/tabler-theme.js --compress --mangle --comments '/@license|@preserve|^!/' --source-map \\\"content=dist/js/tabler-theme.js.map,filename=dist/js/tabler-theme.min.js.map,url=tabler-theme.min.js.map\\\" -o dist/js/tabler-theme.min.js\" \"terser dist/js/tabler-theme.esm.js --module --compress --mangle --comments '/@license|@preserve|^!/' --source-map \\\"content=dist/js/tabler-theme.esm.js.map,filename=dist/js/tabler-theme.esm.min.js.map,url=tabler-theme.esm.min.js.map\\\" -o dist/js/tabler-theme.esm.min.js\"",
"js-build-min-standalone": "concurrently \"terser dist/js/tabler.js --compress --mangle --source-map \\\"content=dist/js/tabler.js.map,filename=dist/js/tabler.min.js.map,url=tabler.min.js.map\\\" -o dist/js/tabler.min.js\" \"terser dist/js/tabler.esm.js --module --compress --mangle --source-map \\\"content=dist/js/tabler.esm.js.map,filename=dist/js/tabler.esm.min.js.map,url=tabler.esm.min.js.map\\\" -o dist/js/tabler.esm.min.js\"",
"js-build-min-theme": "concurrently \"terser dist/js/tabler-theme.js --compress --mangle --source-map \\\"content=dist/js/tabler-theme.js.map,filename=dist/js/tabler-theme.min.js.map,url=tabler-theme.min.js.map\\\" -o dist/js/tabler-theme.min.js\" \"terser dist/js/tabler-theme.esm.js --module --compress --mangle --source-map \\\"content=dist/js/tabler-theme.esm.js.map,filename=dist/js/tabler-theme.esm.min.js.map,url=tabler-theme.esm.min.js.map\\\" -o dist/js/tabler-theme.esm.min.js\"",
"copy": "concurrently \"pnpm run copy-img\" \"pnpm run copy-libs\" \"pnpm run copy-fonts\"",
"copy-img": "shx mkdir -p dist/img && shx cp -rf img/* dist/img",
"copy-libs": "tsx .build/copy-libs.ts",
@@ -67,10 +68,8 @@
"node": ">=20"
},
"files": [
"docs/**/*",
"dist/**/*",
"js/**/*.{ts,js,map}",
"img/**/*.{svg}",
"scss/**/*.scss",
"libs.json"
],
@@ -80,6 +79,7 @@
"umd:main": "dist/js/tabler.min.js",
"module": "dist/js/tabler.esm.js",
"main": "dist/js/tabler.js",
"types": "dist/types/tabler.d.ts",
"bundlewatch": {
"files": [
{
@@ -281,8 +281,5 @@
"typed.js": "^3.0.0",
"typescript": "^7.0.2",
"vitest": "4.1.11"
},
"directories": {
"doc": "docs"
}
}
+16
View File
@@ -114,3 +114,19 @@
.bg-surface-backdrop {
background-color: color-transparent($modal-backdrop-bg, $modal-backdrop-opacity) !important;
}
// Gradient stops that are not theme colors: fade to nothing, or to the inverted surface.
@each $name, $value in ('transparent': transparent, 'inverted': var(--bg-surface-inverted)) {
.bg-gradient-from-#{$name} {
--gradient-from: #{$value};
}
.bg-gradient-to-#{$name} {
--gradient-to: #{$value};
}
.bg-gradient-via-#{$name} {
--gradient-via: #{$value};
--gradient-stops: var(--gradient-from, transparent), var(--gradient-via, transparent), var(--gradient-to, transparent);
}
}
+11
View File
@@ -0,0 +1,11 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": false,
"declaration": true,
"emitDeclarationOnly": true,
"rootDir": "js",
"outDir": "dist/types"
},
"include": ["js/tabler.ts", "js/tabler-theme.ts", "js/src/**/*"]
}
+2 -2
View File
@@ -74,8 +74,8 @@ export default defineConfig({
requiredFile: path('./assets/favicon.ico'),
},
{
// docs css built by this package's sass pipeline (see the `css` and
// `watch-css` scripts, which both write here). Source is tmp-assets/
// docs css built by this package's `css` and `watch-css` scripts
// (both write here). Source is tmp-assets/
// (not public/) because copy-assets wipes public/ on every restart —
// anything a watcher writes straight into public/ is lost there.
from: path('./tmp-assets/css'),
+2 -1
View File
@@ -3,6 +3,7 @@
// markdown content, excluding <!--EXAMPLE-->...<!--/EXAMPLE--> blocks).
import Subheader from '@ui/Subheader.astro'
import Icon from '@ui/Icon.astro'
import illustrations from '@data/illustrations.json'
export interface TocItem {
/** 2 or 3 (h2/h3); h3 gets the ms-3 indent */
@@ -24,7 +25,7 @@ interface Props {
const { toc = [], editUrl, sourceUrl, markdownUrl } = Astro.props
const illustrationsCount = 130
const illustrationsCount = illustrations.length
---
<!-- BEGIN DOCS TOC -->{
+1 -3
View File
@@ -35,9 +35,7 @@ If you load an icon as an image, you can modify its size using CSS.
You can paste the content of the icon file into your HTML code to display it on the page.
<Example codeOnly>
<a href="">
<Icon name="disabled" /> Click me
</a>
<a href=""> <Icon name="disabled" /> Click me </a>
</Example>
Thanks to that, you can change the size, color and the `stroke-width` of the icons with CSS code.
+11
View File
@@ -7,6 +7,7 @@ description: Explore the Tabler color palette and learn how to use base and past
---
import Colors from '@components/Colors.astro'
import Example from '@components/Example.astro'
import site from '@data/site.json'
## Base colors
@@ -27,6 +28,16 @@ The gray palette is a great choice for creating a neutral background for your de
<Colors colors={site.grayColors} />
### Gray utilities
Every shade of the gray palette has a background and a text utility, from `50` to `950`. Use `bg-gray-*` for surfaces and `text-gray-*` for muted text, and pick a pair with enough contrast between them.
<Example>
<div class="bg-gray-100 text-gray-700 p-3 rounded mb-2">Light surface with dark text</div>
<div class="bg-gray-800 text-gray-100 p-3 rounded mb-2">Dark surface with light text</div>
<p class="text-gray-500 mb-0">Muted paragraph with text-gray-500</p>
</Example>
## Social colors
Use the colors of popular social networks to create a recognizable design and make it easier for users to interact with your product.
+3 -5
View File
@@ -27,7 +27,7 @@ This example shows typical content wrapped in `.prose`.
<blockquote>
<p>Documentation is a love letter that you write to your future self.</p>
</blockquote>
<p> The foundation of great documentation starts with <strong>understanding your audience</strong> and their needs. </p>
<p>The foundation of great documentation starts with <strong>understanding your audience</strong> and their needs.</p>
</Prose>
</Example>
@@ -37,9 +37,7 @@ Wrap a short note in `.callout` to make it stand out from the surrounding text,
<Example>
<div class="callout">
<p>
<strong>Tip.</strong> Keep a glossary of terms your readers might not know. It saves you from re-explaining the same concept in every article.
</p>
<p><strong>Tip.</strong> Keep a glossary of terms your readers might not know. It saves you from re-explaining the same concept in every article.</p>
</div>
</Example>
@@ -67,7 +65,7 @@ Inline code is available with `<code>`. For multi-line snippets, use `<pre><code
<Example>
<Prose>
<p> Inline code looks like <code>console.log("Hello")</code> and uses monospace styling. </p>
<p>Inline code looks like <code>console.log("Hello")</code> and uses monospace styling.</p>
<pre><code>// Create a function that returns a sum function add(a, b) &#123; return a + b; &#125; </code></pre>
</Prose>
</Example>
+10 -17
View File
@@ -73,14 +73,11 @@ Here are examples of semantic text elements:
<div><mark>Highlighted</mark></div>
<div><s>Strikethrough</s></div>
<div><samp>Sample</samp></div>
<div> Text <sub>Subscript</sub> </div>
<div> Text <sup>Superscript</sup> </div>
<div>Text <sub>Subscript</sub></div>
<div>Text <sup>Superscript</sup></div>
<div><time>20:00</time></div>
<div><u>Underline</u></div>
<div>
<var>x</var> =
<var>y</var> + 2
</div>
<div><var>x</var> = <var>y</var> + 2</div>
</Example>
## Horizontal rules
@@ -102,11 +99,11 @@ You can also add a label to a horizontal rule and align it as you see fit. Cente
<Example>
<p> Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. </p>
<div class="hr-text"><span>Rule text</span></div>
<p> At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. </p>
<p>At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
<div class="hr-text"><span>Rule text</span></div>
<p> Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. </p>
<div class="hr-text hr-text-start"><span>Rule text</span></div>
<p> At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. </p>
<p>At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</p>
<div class="hr-text hr-text-end"><span>Rule text</span></div>
<p> Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. </p>
</Example>
@@ -152,10 +149,10 @@ Control the tracking (letter spacing) of an element and make it tight, wide or n
Control the leading (line height) of an element using the `.lh-*` classes. The line height is the vertical space between lines of text.
<Example>
<p class="lh-1"> This is the long text with line height 1.<br />Lorem ipsum dolor sit amet.<br />Dolor sit amet. </p>
<p class="lh-sm"> This is the long text with small line height.<br />Lorem ipsum dolor sit amet.<br />Dolor sit amet. </p>
<p class="lh-base"> This is the long text with base line height.<br />Lorem ipsum dolor sit amet.<br />Dolor sit amet. </p>
<p class="lh-lg"> This is the long text with large line height.<br />Lorem ipsum dolor sit amet.<br />Dolor sit amet. </p>
<p class="lh-1">This is the long text with line height 1.<br />Lorem ipsum dolor sit amet.<br />Dolor sit amet.</p>
<p class="lh-sm">This is the long text with small line height.<br />Lorem ipsum dolor sit amet.<br />Dolor sit amet.</p>
<p class="lh-base">This is the long text with base line height.<br />Lorem ipsum dolor sit amet.<br />Dolor sit amet.</p>
<p class="lh-lg">This is the long text with large line height.<br />Lorem ipsum dolor sit amet.<br />Dolor sit amet.</p>
</Example>
## Antialiasing
@@ -174,11 +171,7 @@ Use the `.antialiased` utility to render text using subpixel antialiasing or use
Use the `<kbd>` to indicate input that is typically entered via keyboard.
<Example>
To edit settings, press
<kbd>ctrl</kbd> +
<kbd>,</kbd> or
<kbd>ctrl</kbd> +
<kbd>C</kbd> .
To edit settings, press <kbd>ctrl</kbd> + <kbd>,</kbd> or <kbd>ctrl</kbd> + <kbd>C</kbd> .
</Example>
## Prose
+2 -2
View File
@@ -49,7 +49,7 @@ This preview shows the base structure with one expanded section.
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#overview-item-1" aria-expanded="true" aria-controls="overview-item-1"> Account details <span class="accordion-button-toggle"><Icon name="chevron-down" /></span> </button>
</h2>
<div id="overview-item-1" class="accordion-collapse collapse show" data-bs-parent="#accordion-overview">
<div class="accordion-body"> Update profile fields, contact details, and team role settings in this section. </div>
<div class="accordion-body">Update profile fields, contact details, and team role settings in this section.</div>
</div>
</div>
<div class="accordion-item">
@@ -57,7 +57,7 @@ This preview shows the base structure with one expanded section.
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#overview-item-2" aria-expanded="false" aria-controls="overview-item-2"> Security settings <span class="accordion-button-toggle"><Icon name="chevron-down" /></span> </button>
</h2>
<div id="overview-item-2" class="accordion-collapse collapse" data-bs-parent="#accordion-overview">
<div class="accordion-body"> Configure password policy, two-factor requirements, and active device sessions. </div>
<div class="accordion-body">Configure password policy, two-factor requirements, and active device sessions.</div>
</div>
</div>
<div class="accordion-item">
+6 -5
View File
@@ -217,7 +217,7 @@ Buttons don't inherit the alert's color, so you should set the proper class if y
<Example>
<div class="alert alert-success alert-dismissible" role="alert">
<h3 class="mb-1">Some Title</h3>
<p class="text-secondary"> Lorem ipsum Minim ad pariatur eiusmod ea ut nulla aliqua est quis id dolore minim voluptate. </p>
<p class="text-secondary">Lorem ipsum Minim ad pariatur eiusmod ea ut nulla aliqua est quis id dolore minim voluptate.</p>
<div class="btn-list">
<a href="#" class="btn btn-success">Okay</a>
<a href="#" class="btn">Cancel</a>
@@ -226,7 +226,7 @@ Buttons don't inherit the alert's color, so you should set the proper class if y
</div>
<div class="alert alert-info alert-dismissible" role="alert">
<h3 class="mb-1">Some Title</h3>
<p class="text-secondary"> Lorem ipsum Minim ad pariatur eiusmod ea ut nulla aliqua est quis id dolore minim voluptate. </p>
<p class="text-secondary">Lorem ipsum Minim ad pariatur eiusmod ea ut nulla aliqua est quis id dolore minim voluptate.</p>
<div class="btn-list">
<a href="#" class="btn btn-info">Okay</a>
<a href="#" class="btn">Cancel</a>
@@ -235,7 +235,7 @@ Buttons don't inherit the alert's color, so you should set the proper class if y
</div>
<div class="alert alert-warning alert-dismissible" role="alert">
<h3 class="mb-1">Some Title</h3>
<p class="text-secondary"> Lorem ipsum Minim ad pariatur eiusmod ea ut nulla aliqua est quis id dolore minim voluptate. </p>
<p class="text-secondary">Lorem ipsum Minim ad pariatur eiusmod ea ut nulla aliqua est quis id dolore minim voluptate.</p>
<div class="btn-list">
<a href="#" class="btn btn-warning">Okay</a>
<a href="#" class="btn">Cancel</a>
@@ -244,7 +244,7 @@ Buttons don't inherit the alert's color, so you should set the proper class if y
</div>
<div class="alert alert-danger alert-dismissible" role="alert">
<h3 class="mb-1">Some Title</h3>
<p class="text-secondary"> Lorem ipsum Minim ad pariatur eiusmod ea ut nulla aliqua est quis id dolore minim voluptate. </p>
<p class="text-secondary">Lorem ipsum Minim ad pariatur eiusmod ea ut nulla aliqua est quis id dolore minim voluptate.</p>
<div class="btn-list">
<a href="#" class="btn btn-danger">Okay</a>
<a href="#" class="btn">Cancel</a>
@@ -287,7 +287,8 @@ You can also use other elements, like icons and dismissible buttons, with this t
Links work here too. Both `alert-link` and `alert-action` follow the alert color, so they turn white on the solid background.
<Example>
<Alert variant="important" color="danger" title="An error occurred!" action="Try again" /> <Alert variant="important" color="info" title="Your plan renews next week." link="See the details" />
<Alert variant="important" color="danger" title="An error occurred!" action="Try again" />
<Alert variant="important" color="info" title="Your plan renews next week." link="See the details" />
</Example>
## Custom alert color
+1 -3
View File
@@ -130,9 +130,7 @@ Add a [status indicator](/ui/components/status) to your avatar to show, for inst
<span class="avatar" style="background-image: url(/static/avatars/022m.jpg)">
<span class="badge bg-success"></span>
</span>
<span class="avatar">
<span class="badge bg-warning"></span> SA
</span>
<span class="avatar"> <span class="badge bg-warning"></span> SA </span>
<span class="avatar" style="background-image: url(/static/avatars/022m.jpg)">
<span class="badge bg-info"></span>
</span>
+11 -11
View File
@@ -67,19 +67,19 @@ Use the `.badge-list` container whenever you show more than one badge next to ea
Badges can be used in headings to draw attention to new or important information. You can use them in any heading level, from `<h1>` to `<h6>`. The example below shows how to use badges in headings.
<Example>
<h1> Example heading <Badge text="New" /> </h1>
<h2> Example heading <Badge text="New" /> </h2>
<h3> Example heading <Badge text="New" /> </h3>
<h4> Example heading <Badge text="New" /> </h4>
<h5> Example heading <Badge text="New" /> </h5>
<h6> Example heading <Badge text="New" /> </h6>
<h1>Example heading <Badge text="New" /></h1>
<h2>Example heading <Badge text="New" /></h2>
<h3>Example heading <Badge text="New" /></h3>
<h4>Example heading <Badge text="New" /></h4>
<h5>Example heading <Badge text="New" /></h5>
<h6>Example heading <Badge text="New" /></h6>
</Example>
## Light versions of badges
You can use the `-lt` classes to create a light version of the badge. This is useful for creating a more subtle look.
For example you can use the `bg-blue-lt` class to create a light blue badge. If you add the `text-blue-lt-fg` class, the text will be blue as well.
For example you can use the `bg-blue-lt` class to create a light blue badge. The class also sets the text color, so the label stays readable on the tint.
<Example centered>
<BadgeList>{colors.map(([key, val]) => <Badge color={key} text={val.title} light />)}</BadgeList>
@@ -156,9 +156,9 @@ If you don't provide text for the badge, you end up with a small dot. This is us
<Example centered>
<div class="btn-list">
<button type="button" class="btn"> Notifications <Badge text="2" color="red" class="ms-2" /> </button>
<button type="button" class="btn"> Inbox <Badge text="4" color="red" class="badge-notification" /> </button>
<button type="button" class="btn"> Profile <Badge text="" color="red" class="badge-notification" /> </button>
<button type="button" class="btn">Notifications <Badge text="2" color="red" class="ms-2" /></button>
<button type="button" class="btn">Inbox <Badge text="4" color="red" class="badge-notification" /></button>
<button type="button" class="btn">Profile <Badge text="" color="red" class="badge-notification" /></button>
</div>
</Example>
@@ -167,7 +167,7 @@ If you don't provide text for the badge, you end up with a small dot. This is us
You can use the `.badge-blink` class to create a blinking effect. This class will add a CSS animation to the badge, so it will blink to draw attention.
<Example centered>
<button type="button" class="btn"> Profile <Badge text="" color="red" class="badge-notification badge-blink" /> </button>
<button type="button" class="btn">Profile <Badge text="" color="red" class="badge-notification badge-blink" /></button>
</Example>
## Size options
+34 -99
View File
@@ -213,24 +213,12 @@ See all icons at [tabler.io/icons](https://tabler.io/icons).
<Example centered>
<div class="btn-list">
<button type="button" class="btn">
<Icon name="upload" /> Upload
</button>
<button type="button" class="btn btn-warning">
<Icon name="heart" /> I like
</button>
<button type="button" class="btn btn-success">
<Icon name="check" /> I agree
</button>
<button type="button" class="btn btn-primary">
<Icon name="plus" /> More
</button>
<button type="button" class="btn btn-danger">
<Icon name="link" /> Link
</button>
<button type="button" class="btn btn-info">
<Icon name="message" /> Comment
</button>
<button type="button" class="btn"><Icon name="upload" /> Upload</button>
<button type="button" class="btn btn-warning"><Icon name="heart" /> I like</button>
<button type="button" class="btn btn-success"><Icon name="check" /> I agree</button>
<button type="button" class="btn btn-primary"><Icon name="plus" /> More</button>
<button type="button" class="btn btn-danger"><Icon name="link" /> Link</button>
<button type="button" class="btn btn-info"><Icon name="message" /> Comment</button>
</div>
</Example>
@@ -240,48 +228,20 @@ You can use the icons of popular social networking sites, which users are famili
<Example separated centered hideCode>
<div class="btn-list">
<a href="#" class="btn btn-facebook">
<Icon name="brand-facebook" /> Facebook
</a>
<a href="#" class="btn btn-twitter">
<Icon name="brand-twitter" /> Twitter
</a>
<a href="#" class="btn btn-google">
<Icon name="brand-google" /> Google
</a>
<a href="#" class="btn btn-youtube">
<Icon name="brand-youtube" /> Youtube
</a>
<a href="#" class="btn btn-vimeo">
<Icon name="brand-vimeo" /> Vimeo
</a>
<a href="#" class="btn btn-dribbble">
<Icon name="brand-dribbble" /> Dribbble
</a>
<a href="#" class="btn btn-github">
<Icon name="brand-github" /> Github
</a>
<a href="#" class="btn btn-instagram">
<Icon name="brand-instagram" /> Instagram
</a>
<a href="#" class="btn btn-pinterest">
<Icon name="brand-pinterest" /> Pinterest
</a>
<a href="#" class="btn btn-vk">
<Icon name="brand-vk" /> VK
</a>
<a href="#" class="btn btn-rss">
<Icon name="brand-rss" /> RSS
</a>
<a href="#" class="btn btn-flickr">
<Icon name="brand-flickr" /> Flickr
</a>
<a href="#" class="btn btn-bitbucket">
<Icon name="brand-bitbucket" /> Bitbucket
</a>
<a href="#" class="btn btn-tabler">
<Icon name="brand-tabler" /> Tabler
</a>
<a href="#" class="btn btn-facebook"> <Icon name="brand-facebook" /> Facebook </a>
<a href="#" class="btn btn-twitter"> <Icon name="brand-twitter" /> Twitter </a>
<a href="#" class="btn btn-google"> <Icon name="brand-google" /> Google </a>
<a href="#" class="btn btn-youtube"> <Icon name="brand-youtube" /> Youtube </a>
<a href="#" class="btn btn-vimeo"> <Icon name="brand-vimeo" /> Vimeo </a>
<a href="#" class="btn btn-dribbble"> <Icon name="brand-dribbble" /> Dribbble </a>
<a href="#" class="btn btn-github"> <Icon name="brand-github" /> Github </a>
<a href="#" class="btn btn-instagram"> <Icon name="brand-instagram" /> Instagram </a>
<a href="#" class="btn btn-pinterest"> <Icon name="brand-pinterest" /> Pinterest </a>
<a href="#" class="btn btn-vk"> <Icon name="brand-vk" /> VK </a>
<a href="#" class="btn btn-rss"> <Icon name="brand-rss" /> RSS </a>
<a href="#" class="btn btn-flickr"> <Icon name="brand-flickr" /> Flickr </a>
<a href="#" class="btn btn-bitbucket"> <Icon name="brand-bitbucket" /> Bitbucket </a>
<a href="#" class="btn btn-tabler"> <Icon name="brand-tabler" /> Tabler </a>
</div>
</Example>
@@ -369,16 +329,14 @@ Create a [dropdown](/ui/components/dropdown) button that will encourage users to
</div>
</div>
<div class="dropdown">
<button type="button" class="btn dropdown-toggle" data-bs-toggle="dropdown">
<Icon name="calendar" /> Show calendar
</button>
<button type="button" class="btn dropdown-toggle" data-bs-toggle="dropdown"><Icon name="calendar" /> Show calendar</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
</div>
</div>
<div class="dropdown">
<button type="button" class="btn dropdown-toggle" data-bs-toggle="dropdown"> Show calendar </button>
<button type="button" class="btn dropdown-toggle" data-bs-toggle="dropdown">Show calendar</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
@@ -413,9 +371,7 @@ The class hides the label and draws a spinner in its place. It sets `pointer-eve
</Example>
<Example centered>
<a href="#" class="btn btn-primary">
<span class="spinner-border spinner-border-sm me-2" role="status"></span> Button
</a>
<a href="#" class="btn btn-primary"> <span class="spinner-border spinner-border-sm me-2" role="status"></span> Button </a>
</Example>
## Full width buttons
@@ -423,8 +379,7 @@ The class hides the label and draws a spinner in its place. It sets `pointer-eve
Add the `.w-100` class to make buttons span the full width of their container. This is useful for mobile-first designs or when you want buttons to take up the entire available space.
<Example separated>
<a href="#" class="btn btn-primary w-100">Full width button</a>
<a href="#" class="btn btn-outline-secondary w-100">Full width outline button</a>
<a href="#" class="btn btn-primary w-100">Full width button</a> <a href="#" class="btn btn-outline-secondary w-100">Full width outline button</a>
</Example>
## List of buttons
@@ -507,15 +462,9 @@ Use buttons with [avatars](/ui/components/avatar) to simplify the process of int
<Example centered>
<div class="btn-list">
<a href="#" class="btn">
<span class="avatar" style="background-image: url(/static/avatars/002f.jpg);"></span> Avatar
</a>
<a href="#" class="btn">
<span class="avatar" style=" background-image: url(/static/avatars/002m.jpg); "></span> Avatar
</a>
<a href="#" class="btn">
<span class="avatar" style=" background-image: url(/static/avatars/004f.jpg); "></span> Avatar
</a>
<a href="#" class="btn"> <span class="avatar" style="background-image: url(/static/avatars/002f.jpg);"></span> Avatar </a>
<a href="#" class="btn"> <span class="avatar" style=" background-image: url(/static/avatars/002m.jpg); "></span> Avatar </a>
<a href="#" class="btn"> <span class="avatar" style=" background-image: url(/static/avatars/004f.jpg); "></span> Avatar </a>
</div>
</Example>
@@ -526,28 +475,14 @@ Add a subtle animation effect to your buttons when users hover over them. This c
<Example>
<div class="btn-list">
<a class="btn btn-animate-icon"> Save <Icon name="arrow-right" class="icon-end" /> </a>
<a class="btn btn-animate-icon btn-animate-icon-rotate">
<Icon name="plus" /> Add
</a>
<a class="btn btn-animate-icon btn-animate-icon-shake">
<Icon name="bell" /> Notifications
</a>
<a class="btn btn-animate-icon btn-animate-icon-rotate">
<Icon name="settings" /> Settings
</a>
<a class="btn btn-animate-icon btn-animate-icon-pulse">
<Icon name="heart" /> Love
</a>
<a class="btn btn-animate-icon btn-animate-icon-rotate">
<Icon name="x" /> Close
</a>
<a class="btn btn-animate-icon btn-animate-icon-tada">
<Icon name="check" /> Confirm
</a>
<a class="btn btn-animate-icon btn-animate-icon-rotate"> <Icon name="plus" /> Add </a>
<a class="btn btn-animate-icon btn-animate-icon-shake"> <Icon name="bell" /> Notifications </a>
<a class="btn btn-animate-icon btn-animate-icon-rotate"> <Icon name="settings" /> Settings </a>
<a class="btn btn-animate-icon btn-animate-icon-pulse"> <Icon name="heart" /> Love </a>
<a class="btn btn-animate-icon btn-animate-icon-rotate"> <Icon name="x" /> Close </a>
<a class="btn btn-animate-icon btn-animate-icon-tada"> <Icon name="check" /> Confirm </a>
<a class="btn btn-animate-icon"> Next <Icon name="chevron-right" class="icon-end" /> </a>
<a class="btn btn-animate-icon btn-animate-icon-move-start">
<Icon name="chevron-left" /> Previous
</a>
<a class="btn btn-animate-icon btn-animate-icon-move-start"> <Icon name="chevron-left" /> Previous </a>
</div>
</Example>
+6 -10
View File
@@ -143,7 +143,7 @@ Add an image to your blog post card to make it eye-catching. You can do it by ad
<a href="#"><img class="card-img-top" src="/static/photos/book-on-the-grass.jpg" alt="" /></a>
<div class="card-body d-flex flex-column">
<h3 class="card-title"><a href="#">How do you know she is a witch?</a></h3>
<div class="text-secondary"> Are you suggesting that coconuts migrate? No, no, no! Yes, yes. A bit. But she's got a wart. You ... </div>
<div class="text-secondary">Are you suggesting that coconuts migrate? No, no, no! Yes, yes. A bit. But she's got a wart. You ...</div>
<div class="d-flex align-items-center pt-4 mt-auto">
<span class="avatar" style="background-image: url(/static/avatars/023m.jpg)"></span>
<div class="ms-3">
@@ -171,7 +171,7 @@ Add the `.row-deck` class to `.row`, if you want to display several cards next t
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body"> Extra long content of card. Lorem ipsum dolor sit amet, consetetur sadipscing elitr </div>
<div class="card-body">Extra long content of card. Lorem ipsum dolor sit amet, consetetur sadipscing elitr</div>
</div>
</div>
<div class="col-md-4">
@@ -195,7 +195,7 @@ You can also put the image beside the text instead of above it. Use a `row row-0
<div class="col">
<div class="card-body h-full d-flex flex-column">
<h3 class="card-title"><a href="#">Shut up!</a></h3>
<div class="text-secondary"> Burn her! How do you know she is a witch? You don't frighten us, English pig-dogs! Go and boil yo... </div>
<div class="text-secondary">Burn her! How do you know she is a witch? You don't frighten us, English pig-dogs! Go and boil yo...</div>
<div class="d-flex align-items-center pt-4 mt-auto">
<span class="avatar" style="background-image: url(/static/avatars/029m.jpg)"></span>
<div class="ms-3">
@@ -254,7 +254,7 @@ Add a `.card-stamp` element as a direct child of the card, before the card body.
</div>
<div class="card-body">
<h3 class="card-title">Card with a stamp</h3>
<p class="text-secondary"> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam deleniti fugit incidunt, iste, itaque minima neque pariatur. </p>
<p class="text-secondary">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam deleniti fugit incidunt, iste, itaque minima neque pariatur.</p>
</div>
</div>
</Example>
@@ -398,9 +398,7 @@ Use `card-actions` for buttons and links in a card header. It keeps them aligned
<div class="card-header">
<h3 class="card-title">Team members</h3>
<div class="card-actions">
<a href="#" class="btn btn-primary">
<Icon name="plus" /> Invite
</a>
<a href="#" class="btn btn-primary"> <Icon name="plus" /> Invite </a>
</div>
</div>
<div class="card-body">Invite a teammate to this project.</div>
@@ -439,9 +437,7 @@ Change how solid a card looks. `card-borderless` drops the border, `card-dashed`
</div>
<div class="col-md-4">
<div class="card card-transparent">
<div class="card-body text-secondary">
<Icon name="plus" /> Add new
</div>
<div class="card-body text-secondary"><Icon name="plus" /> Add new</div>
</div>
</div>
</div>
+5 -5
View File
@@ -36,7 +36,7 @@ Add `chat-bubble-me` to bubbles the current user sent. Tabler tints them, and yo
<Example background="primary">
<div class="chat">
<div class="chat-bubbles">
<div class="chat-item">
<div>
<div class="row align-items-end">
<div class="col-auto">
<span class="avatar" style="background-image: url(/static/avatars/016f.jpg)"></span>
@@ -54,7 +54,7 @@ Add `chat-bubble-me` to bubbles the current user sent. Tabler tints them, and yo
</div>
</div>
</div>
<div class="chat-item">
<div>
<div class="row align-items-end justify-content-end">
<div class="col-auto">
<div class="chat-bubble chat-bubble-me">
@@ -89,7 +89,7 @@ While you wait for a reply, show a bubble with `animated-dots` after the text
<Example background="primary">
<div class="chat-bubble">
<div class="chat-bubble-body">
<p class="text-secondary fst-italic mb-0"> Alice is typing <span class="animated-dots"></span> </p>
<p class="text-secondary fst-italic mb-0">Alice is typing <span class="animated-dots"></span></p>
</div>
</div>
</Example>
@@ -133,7 +133,7 @@ A card with a scrollable message list and a message input in the footer.
<div class="card-body">
<div class="chat">
<div class="chat-bubbles">
<div class="chat-item">
<div>
<div class="row align-items-end">
<div class="col-auto">
<span class="avatar avatar-xs" style="background-image: url(/static/avatars/016f.jpg)"></span>
@@ -145,7 +145,7 @@ A card with a scrollable message list and a message input in the footer.
</div>
</div>
</div>
<div class="chat-item">
<div>
<div class="row align-items-end justify-content-end">
<div class="col-auto">
<div class="chat-bubble chat-bubble-me">
+1 -3
View File
@@ -101,9 +101,7 @@ A value is a plain container, so it takes anything: text, [avatars](/ui/componen
</div>
<div class="datagrid-item">
<div class="datagrid-title">Icon</div>
<div class="datagrid-content">
<Icon name="check" class="text-green" /> Checked
</div>
<div class="datagrid-content"><Icon name="check" class="text-green" /> Checked</div>
</div>
<div class="datagrid-item">
<div class="datagrid-title">Form control</div>
+10 -10
View File
@@ -29,9 +29,9 @@ import Example from '@components/Example.astro';
Use dividers to visually separate content into parts. You can use a line only or add text that will be centered by default.
<Example>
<p> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias, dolore dolores doloribus est ex. </p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias, dolore dolores doloribus est ex.</p>
<div class="hr-text">See also</div>
<p> Dicta error hic illo iure necessitatibus nihil officiis omnis perferendis, praesentium repellendus rerum, saepe sed, sit! </p>
<p>Dicta error hic illo iure necessitatibus nihil officiis omnis perferendis, praesentium repellendus rerum, saepe sed, sit!</p>
</Example>
## Text position
@@ -39,13 +39,13 @@ Use dividers to visually separate content into parts. You can use a line only or
You can modify the position of the text which is to be included in a separator and make it left- or right-aligned. Otherwise, the text will remain centered.
<Example>
<p> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias, dolore dolores doloribus est ex. </p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias, dolore dolores doloribus est ex.</p>
<div class="hr-text hr-text-start">Start divider</div>
<p> Dicta error hic illo iure necessitatibus nihil officiis omnis perferendis, praesentium repellendus rerum, saepe sed, sit! </p>
<p>Dicta error hic illo iure necessitatibus nihil officiis omnis perferendis, praesentium repellendus rerum, saepe sed, sit!</p>
<div class="hr-text">Centered divider</div>
<p> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias, dolore dolores doloribus est ex. </p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias, dolore dolores doloribus est ex.</p>
<div class="hr-text hr-text-end">End divider</div>
<p> Dicta error hic illo iure necessitatibus nihil officiis omnis perferendis, praesentium repellendus rerum, saepe sed, sit! </p>
<p>Dicta error hic illo iure necessitatibus nihil officiis omnis perferendis, praesentium repellendus rerum, saepe sed, sit!</p>
</Example>
## Divider color
@@ -53,13 +53,13 @@ You can modify the position of the text which is to be included in a separator a
Customize the color of dividers to make them go well with your design. See the [full list of available colors](/ui/base/colors) for more details.
<Example>
<p> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias, dolore dolores doloribus est ex. </p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias, dolore dolores doloribus est ex.</p>
<div class="hr-text text-green">Green divider</div>
<p> Dicta error hic illo iure necessitatibus nihil officiis omnis perferendis, praesentium repellendus rerum, saepe sed, sit! </p>
<p>Dicta error hic illo iure necessitatibus nihil officiis omnis perferendis, praesentium repellendus rerum, saepe sed, sit!</p>
<div class="hr-text text-red">Red divider</div>
<p> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias, dolore dolores doloribus est ex. </p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias, dolore dolores doloribus est ex.</p>
<div class="hr-text text-primary">Primary divider</div>
<p> Dicta error hic illo iure necessitatibus nihil officiis omnis perferendis, praesentium repellendus rerum, saepe sed, sit! </p>
<p>Dicta error hic illo iure necessitatibus nihil officiis omnis perferendis, praesentium repellendus rerum, saepe sed, sit!</p>
</Example>
## Accessibility
+4 -10
View File
@@ -179,15 +179,9 @@ Use dropdowns with [checkboxes](/ui/forms/elements) to allow users to select opt
<div class="dropdown">
<a href="#" class="btn dropdown-toggle" data-bs-toggle="dropdown">Open dropdown</a>
<div class="dropdown-menu">
<label class="dropdown-item">
<input class="form-check-input m-0 me-2" type="radio" /> Option 1
</label>
<label class="dropdown-item">
<input class="form-check-input m-0 me-2" type="radio" /> Option 2
</label>
<label class="dropdown-item">
<input class="form-check-input m-0 me-2" type="radio" /> Option 3
</label>
<label class="dropdown-item"> <input class="form-check-input m-0 me-2" type="radio" /> Option 1 </label>
<label class="dropdown-item"> <input class="form-check-input m-0 me-2" type="radio" /> Option 2 </label>
<label class="dropdown-item"> <input class="form-check-input m-0 me-2" type="radio" /> Option 3 </label>
</div>
</div>
</Example>
@@ -225,7 +219,7 @@ Use a dropdown with [card](/ui/components/card) content to make it easy for user
</a>
<div class="card-body d-flex flex-column">
<h3 class="card-title"><a href="#">How do you know she is a witch?</a></h3>
<div class="text-secondary"> Are you suggesting that coconuts migrate? No, no, no! Yes, yes. A bit. </div>
<div class="text-secondary">Are you suggesting that coconuts migrate? No, no, no! Yes, yes. A bit.</div>
<div class="d-flex align-items-center pt-4 mt-auto">
<span class="avatar" style="background-image: url(/static/avatars/013m.jpg)"></span>
<div class="ms-3">
+6 -12
View File
@@ -38,11 +38,9 @@ Use the default empty state to engage users in the critical moments of their exp
<div class="empty">
<div class="empty-icon"><Icon name="mood-sad" /></div>
<p class="empty-title">No results found</p>
<p class="empty-subtitle text-secondary"> Try adjusting your search or filter to find what you're looking for. </p>
<p class="empty-subtitle text-secondary">Try adjusting your search or filter to find what you're looking for.</p>
<div class="empty-action">
<a href="#" class="btn btn-primary">
<Icon name="search" /> Search again
</a>
<a href="#" class="btn btn-primary"> <Icon name="search" /> Search again </a>
</div>
</div>
</Example>
@@ -57,11 +55,9 @@ Do you need an illustration to enhance your web designs? We've got something for
<div class="empty">
<div class="empty-img"><Illustration image="boy-with-key" alt="Empty state illustration" /></div>
<p class="empty-title">Invoices are managed from here</p>
<p class="empty-subtitle text-secondary"> Try adjusting your search or filter to find what you're looking for. </p>
<p class="empty-subtitle text-secondary">Try adjusting your search or filter to find what you're looking for.</p>
<div class="empty-action">
<a href="#" class="btn btn-primary">
<Icon name="plus" /> New invoice
</a>
<a href="#" class="btn btn-primary"> <Icon name="plus" /> New invoice </a>
</div>
</div>
</Example>
@@ -74,11 +70,9 @@ Instead of adding an icon or illustration you can simply give the text:
<div class="empty">
<div class="empty-header">404</div>
<p class="empty-title">Oops… You just found an error page</p>
<p class="empty-subtitle text-secondary"> Try adjusting your search or filter to find what you're looking for. </p>
<p class="empty-subtitle text-secondary">Try adjusting your search or filter to find what you're looking for.</p>
<div class="empty-action">
<a href="#" class="btn btn-primary">
<Icon name="arrow-left" /> Take me home
</a>
<a href="#" class="btn btn-primary"> <Icon name="arrow-left" /> Take me home </a>
</div>
</div>
</Example>
+5 -11
View File
@@ -145,15 +145,9 @@ Add `list-group-transparent` to drop the background and the borders. Icons insid
<Example bg="surface-secondary" column>
<div class="list-group list-group-transparent">
<a href="#" class="list-group-item list-group-item-action d-flex align-items-center">
<Icon name="user" class="me-2" /> Account
</a>
<a href="#" class="list-group-item list-group-item-action d-flex align-items-center active">
<Icon name="settings" class="me-2" /> Settings
</a>
<a href="#" class="list-group-item list-group-item-action d-flex align-items-center">
<Icon name="bell" class="me-2" /> Notifications
</a>
<a href="#" class="list-group-item list-group-item-action d-flex align-items-center"> <Icon name="user" class="me-2" /> Account </a>
<a href="#" class="list-group-item list-group-item-action d-flex align-items-center active"> <Icon name="settings" class="me-2" /> Settings </a>
<a href="#" class="list-group-item list-group-item-action d-flex align-items-center"> <Icon name="bell" class="me-2" /> Notifications </a>
</div>
</Example>
@@ -245,7 +239,7 @@ This pattern joins several variants: a flush list inside a card, hoverable rows,
</div>
<div class="col text-truncate">
<a href="#" class="text-body d-block">Build failed</a>
<div class="d-block text-secondary text-truncate mt-n1"> The last deploy did not finish </div>
<div class="d-block text-secondary text-truncate mt-n1">The last deploy did not finish</div>
</div>
<div class="col-auto">
<a href="#" class="list-group-item-actions" aria-label="Add to favorites"><Icon name="star" color="muted" /></a>
@@ -257,7 +251,7 @@ This pattern joins several variants: a flush list inside a card, hoverable rows,
<div class="col-auto"><span class="status-dot d-block"></span></div>
<div class="col text-truncate">
<a href="#" class="text-body d-block">New comment</a>
<div class="d-block text-secondary text-truncate mt-n1"> Alice replied in the design thread </div>
<div class="d-block text-secondary text-truncate mt-n1">Alice replied in the design thread</div>
</div>
<div class="col-auto">
<a href="#" class="list-group-item-actions show" aria-label="Remove from favorites"><Icon name="star" color="yellow" /></a>
+10 -10
View File
@@ -26,7 +26,8 @@ import Example from '@components/Example.astro'
Use the `mention` class for a chip inside a sentence. It works on a `span`, or on an `a` when the chip should link somewhere.
<Example centered background="primary">
Hey <a href="#" class="mention"><span class="mention-avatar" style="background-image: url(/static/avatars/035f.jpg)"></span><span class="visually-hidden">@</span>JohnDoe</a>, have you seen the latest updates?
Hey
<a href="#" class="mention"><span class="mention-avatar" style="background-image: url(/static/avatars/035f.jpg)"></span><span class="visually-hidden">@</span>JohnDoe</a>, have you seen the latest updates?
</Example>
## Mention with an avatar
@@ -34,8 +35,8 @@ Hey <a href="#" class="mention"><span class="mention-avatar" style="background-i
Put a `mention-avatar` first inside the chip to show who it refers to. The picture is set as a background image.
<Example centered background="primary">
<a href="#" class="mention"><span class="mention-avatar" style="background-image: url(/static/avatars/035f.jpg)"></span><span class="visually-hidden">@</span>JaneSmith</a>
<a href="#" class="mention"><span class="mention-avatar" style="background-image: url(/static/avatars/029m.jpg)"></span><span class="visually-hidden">@</span>EganPoetz</a>
<a href="#" class="mention"><span class="mention-avatar" style="background-image: url(/static/avatars/035f.jpg)"></span><span class="visually-hidden">@</span>JaneSmith</a>
<a href="#" class="mention"><span class="mention-avatar" style="background-image: url(/static/avatars/029m.jpg)"></span><span class="visually-hidden">@</span>EganPoetz</a>
</Example>
## Mention with an app
@@ -43,8 +44,8 @@ Put a `mention-avatar` first inside the chip to show who it refers to. The pictu
`mention-app` shows a square logo instead of a round picture, for a service or a brand.
<Example centered background="primary">
<a href="#" class="mention"><span class="mention-app" style="background-image: url(/static/brands/netflix.svg)"></span>Netflix</a>
<a href="#" class="mention"><span class="mention-app" style="background-image: url(/static/brands/github.svg)"></span>GitHub</a>
<a href="#" class="mention"><span class="mention-app" style="background-image: url(/static/brands/netflix.svg)"></span>Netflix</a>
<a href="#" class="mention"><span class="mention-app" style="background-image: url(/static/brands/github.svg)"></span>GitHub</a>
</Example>
## Mention with a color
@@ -52,9 +53,9 @@ Put a `mention-avatar` first inside the chip to show who it refers to. The pictu
`mention-color` is a swatch you tint with any `bg-*` utility, so a color value can be named and shown at once.
<Example centered background="primary">
<span class="mention"><span class="mention-color bg-blue"></span>#066fd1</span>
<span class="mention"><span class="mention-color bg-green"></span>rgb(47, 179, 68)</span>
<span class="mention"><span class="mention-color bg-orange"></span>hsl(24deg, 94%, 50%)</span>
<span class="mention"><span class="mention-color bg-blue"></span>#066fd1</span>
<span class="mention"><span class="mention-color bg-green"></span>rgb(47, 179, 68)</span>
<span class="mention"><span class="mention-color bg-orange"></span>hsl(24deg, 94%, 50%)</span>
</Example>
## Mention with a count
@@ -62,8 +63,7 @@ Put a `mention-avatar` first inside the chip to show who it refers to. The pictu
Add `mention-count` at the end for a muted number, for example how many posts carry a tag.
<Example centered background="primary">
<a href="#" class="mention">#WebDevelopment<span class="mention-count">16</span></a>
<a href="#" class="mention">#Tabler<span class="mention-count">4</span></a>
<a href="#" class="mention">#WebDevelopment<span class="mention-count">16</span></a> <a href="#" class="mention">#Tabler<span class="mention-count">4</span></a>
</Example>
## Accessibility
+14 -10
View File
@@ -68,7 +68,7 @@ To create a modal, you need to add a `.modal` element to the document. Inside th
Look at the example below to see how the modal looks.
<Example centered>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#exampleModal"> Launch demo modal </button>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#exampleModal">Launch demo modal</button>
<div class="modal" id="exampleModal" tabindex="-1">
<div class="modal-dialog" role="document">
<div class="modal-content">
@@ -91,7 +91,7 @@ Look at the example below to see how the modal looks.
You can use the modal to create a prompt or alert. Look at the example below to see how the prompt and alert look.
<Example raw>
<div class="modal" id="exampleModal" tabindex="-1">
<div class="modal" id="exampleModal2" tabindex="-1">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content">
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
@@ -103,7 +103,7 @@ You can use the modal to create a prompt or alert. Look at the example below to
<path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75" />
</svg>
<h3>Are you sure?</h3>
<div class="text-secondary"> Do you really want to remove 84 files? What you've done cannot be undone. </div>
<div class="text-secondary">Do you really want to remove 84 files? What you've done cannot be undone.</div>
</div>
<div class="modal-footer">
<div class="w-100">
@@ -123,7 +123,7 @@ You can use the modal to create a prompt or alert. Look at the example below to
</Example>
<Example raw>
<div class="modal" id="exampleModal" tabindex="-1">
<div class="modal" id="exampleModal3" tabindex="-1">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content">
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
@@ -131,7 +131,7 @@ You can use the modal to create a prompt or alert. Look at the example below to
<div class="modal-body text-center py-4">
<Icon name="circle-check" class="mb-2 text-green icon-lg" />
<h3>Payment succeeded</h3>
<div class="text-secondary"> Your payment of $290 has been successfully submitted. Your invoice has been sent to support@tabler.io. </div>
<div class="text-secondary">Your payment of $290 has been successfully submitted. Your invoice has been sent to support@tabler.io.</div>
</div>
<div class="modal-footer">
<div class="w-100">
@@ -155,7 +155,7 @@ You can use the modal to create a prompt or alert. Look at the example below to
You can use the modal to create a form. Look at the example below to see how the form looks.
<Example raw class="px-5">
<div class="modal" id="exampleModal" tabindex="-1">
<div class="modal" id="exampleModal4" tabindex="-1">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
@@ -240,9 +240,7 @@ You can use the modal to create a form. Look at the example below to see how the
</div>
<div class="modal-footer">
<a href="#" class="btn btn-link link-secondary" data-bs-dismiss="modal">Cancel</a>
<a href="#" class="btn btn-primary ms-auto" data-bs-dismiss="modal">
<Icon name="plus" /> Create new report
</a>
<a href="#" class="btn btn-primary ms-auto" data-bs-dismiss="modal"> <Icon name="plus" /> Create new report </a>
</div>
</div>
</div>
@@ -254,7 +252,13 @@ You can use the modal to create a form. Look at the example below to see how the
Add `modal-blur` next to `modal` to blur the page behind the dialog instead of only dimming it.
```html
<div class="modal modal-blur fade" id="modal-blurred" tabindex="-1" role="dialog" aria-hidden="true">
<div
class="modal modal-blur fade"
id="modal-blurred"
tabindex="-1"
role="dialog"
aria-hidden="true"
>
<!-- modal-dialog -->
</div>
```
+4 -4
View File
@@ -42,8 +42,8 @@ Offcanvas is a sidebar component that can be toggled via JavaScript to appear fr
To create an offcanvas, add the `.offcanvas` class to a container element. You can also add the `.offcanvas-start`, `.offcanvas-end`, `.offcanvas-top`, or `.offcanvas-bottom` class to specify the position of the offcanvas. The `.show` class is used to display the offcanvas.
<Example raw code={`<div class="offcanvas offcanvas-start" tabindex="-1" id="offcanvas" aria-labelledby="offcanvasLabel" data-bs-scroll="true"> <div class="offcanvas-body"> Content for the offcanvas goes here. You can place just about any Tabler component or custom elements here. </div> </div>`}>
<div class="offcanvas offcanvas-start show position-static" tabindex="-1" id="offcanvas" aria-labelledby="offcanvasLabel" data-bs-scroll="true" > <div class="offcanvas-body"> Content for the offcanvas goes here. You can place just about any Tabler component or custom elements here. </div> </div>
<Example raw code={`<div class="offcanvas offcanvas-start" tabindex="-1" id="offcanvas" aria-label="Sidebar" data-bs-scroll="true"> <div class="offcanvas-body"> Content for the offcanvas goes here. You can place just about any Tabler component or custom elements here. </div> </div>`}>
<div class="offcanvas offcanvas-start show position-static" tabindex="-1" id="offcanvas" aria-label="Sidebar" data-bs-scroll="true" > <div class="offcanvas-body"> Content for the offcanvas goes here. You can place just about any Tabler component or custom elements here. </div> </div>
</Example>
## Header, title and footer
@@ -60,8 +60,8 @@ Use `offcanvas-title` for the heading, and point the panel's `aria-labelledby` a
Add `offcanvas-narrow` for a panel thinner than the default. It suits a short list of filters or settings, where the full width would leave the panel mostly empty.
<Example raw code={`<div class="offcanvas offcanvas-end offcanvas-narrow" tabindex="-1" id="offcanvas-header" aria-labelledby="offcanvas-header-label"> <div class="offcanvas-header"> <h2 class="offcanvas-title" id="offcanvas-header-label">Filters</h2> <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button> </div> <div class="offcanvas-body">Pick the filters you want to apply.</div> <div class="offcanvas-footer"> <button type="button" class="btn btn-primary w-100">Apply</button> </div> </div>`}>
<div class="offcanvas offcanvas-end offcanvas-narrow show position-static" tabindex="-1" id="offcanvas-header" aria-labelledby="offcanvas-header-label"> <div class="offcanvas-header"> <h2 class="offcanvas-title" id="offcanvas-header-label">Filters</h2> <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button> </div> <div class="offcanvas-body">Pick the filters you want to apply.</div> <div class="offcanvas-footer"> <button type="button" class="btn btn-primary w-100">Apply</button> </div> </div>
<Example raw code={`<div class="offcanvas offcanvas-end offcanvas-narrow" tabindex="-1" id="offcanvas-narrow" aria-labelledby="offcanvas-narrow-label"> <div class="offcanvas-header"> <h2 class="offcanvas-title" id="offcanvas-narrow-label">Filters</h2> <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button> </div> <div class="offcanvas-body">Pick the filters you want to apply.</div> <div class="offcanvas-footer"> <button type="button" class="btn btn-primary w-100">Apply</button> </div> </div>`}>
<div class="offcanvas offcanvas-end offcanvas-narrow show position-static" tabindex="-1" id="offcanvas-narrow" aria-labelledby="offcanvas-narrow-label"> <div class="offcanvas-header"> <h2 class="offcanvas-title" id="offcanvas-narrow-label">Filters</h2> <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button> </div> <div class="offcanvas-body">Pick the filters you want to apply.</div> <div class="offcanvas-footer"> <button type="button" class="btn btn-primary w-100">Apply</button> </div> </div>
</Example>
## Cookies banner
+16 -6
View File
@@ -44,7 +44,11 @@ import CodeDocs from '@components/CodeDocs.astro';
To create a default progress bar, add a `.progress` class to a `<div>` element. Thanks to that, you will be able to notify users how long they have to wait for a process to complete.
<Example>
<div class="progress"><div class="progress-bar" style="width: 38%" role="progressbar" aria-valuenow="38" aria-valuemin="0" aria-valuemax="100" aria-label="38% Complete"><span class="visually-hidden">38% Complete</span></div></div>
<div class="progress">
<div class="progress-bar" style="width: 38%" role="progressbar" aria-valuenow="38" aria-valuemin="0" aria-valuemax="100" aria-label="38% Complete">
<span class="visually-hidden">38% Complete</span>
</div>
</div>
</Example>
## Progress size
@@ -86,13 +90,13 @@ If you want to display the numeric value inside the bar, place the percentage te
<Example columnFullWidth>
<div class="progress progress-lg">
<div class="progress-bar" style="width: 10%" role="progressbar" aria-valuenow="10" aria-valuemin="0" aria-valuemax="100" aria-label="10% Complete"> 10% </div>
<div class="progress-bar" style="width: 10%" role="progressbar" aria-valuenow="10" aria-valuemin="0" aria-valuemax="100" aria-label="10% Complete">10%</div>
</div>
<div class="progress progress-lg">
<div class="progress-bar" style="width: 35%" role="progressbar" aria-valuenow="35" aria-valuemin="0" aria-valuemax="100" aria-label="35% Complete"> 35% </div>
<div class="progress-bar" style="width: 35%" role="progressbar" aria-valuenow="35" aria-valuemin="0" aria-valuemax="100" aria-label="35% Complete">35%</div>
</div>
<div class="progress progress-lg">
<div class="progress-bar" style="width: 90%" role="progressbar" aria-valuenow="90" aria-valuemin="0" aria-valuemax="100" aria-label="90% Complete"> 90% </div>
<div class="progress-bar" style="width: 90%" role="progressbar" aria-valuenow="90" aria-valuemin="0" aria-valuemax="100" aria-label="90% Complete">90%</div>
</div>
</Example>
@@ -172,7 +176,11 @@ This is how it looks:
You can create a striped progress bar by adding the `.progress-bar-striped` class to the `.progress-bar` element.
<Example>
<div class="progress"><div class="progress-bar progress-bar-striped" style="width: 60%" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" aria-label="60% Complete"><span class="visually-hidden">60% Complete</span></div></div>
<div class="progress">
<div class="progress-bar progress-bar-striped" style="width: 60%" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" aria-label="60% Complete">
<span class="visually-hidden">60% Complete</span>
</div>
</div>
</Example>
## Animated progress
@@ -181,7 +189,9 @@ To animate stripes, add the `.progress-bar-animated` class to a striped progress
<Example columnFullWidth>
<div class="progress">
<div class="progress-bar progress-bar-striped progress-bar-animated" style="width: 40%" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" aria-label="40% Complete"><span class="visually-hidden">40% Complete</span></div>
<div class="progress-bar progress-bar-striped progress-bar-animated" style="width: 40%" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" aria-label="40% Complete">
<span class="visually-hidden">40% Complete</span>
</div>
</div>
</Example>
@@ -24,9 +24,9 @@ To create a segmented control, use the `nav` element with the `nav-segmented` cl
<Example centered>
<nav class="nav nav-segmented" role="tablist">
<button class="nav-link active" role="tab" data-bs-toggle="tab" aria-selected="true" aria-current="page"> First </button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1"> Second </button>
<button class="nav-link" disabled role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1"> Disabled </button>
<button class="nav-link active" role="tab" data-bs-toggle="tab" aria-selected="true" aria-current="page">First</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1">Second</button>
<button class="nav-link" disabled role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1">Disabled</button>
</nav>
</Example>
@@ -42,11 +42,11 @@ The results can be seen in the example below.
<Example>
<nav class="nav nav-segmented w-100" role="tablist">
<button class="nav-link active" role="tab" data-bs-toggle="tab" aria-selected="true" aria-current="page"> Daily </button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1"> Weekly </button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1"> Monthly </button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1"> Quarterly </button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1"> Yearly </button>
<button class="nav-link active" role="tab" data-bs-toggle="tab" aria-selected="true" aria-current="page">Daily</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1">Weekly</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1">Monthly</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1">Quarterly</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1">Yearly</button>
</nav>
</Example>
@@ -56,12 +56,12 @@ You can also use emojis in the segmented control. To do this, add the emoji insi
<Example centered>
<nav class="nav nav-segmented" role="tablist">
<button class="nav-link active" role="tab" data-bs-toggle="tab" aria-selected="true" aria-current="page"> 👦 </button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1"> 👦🏿 </button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1"> 👦🏾 </button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1"> 👦🏽 </button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1"> 👦🏼 </button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1"> 👦🏻 </button>
<button class="nav-link active" role="tab" data-bs-toggle="tab" aria-selected="true" aria-current="page">👦</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1">👦🏿</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1">👦🏾</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1">👦🏽</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1">👦🏼</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1">👦🏻</button>
</nav>
</Example>
@@ -72,18 +72,10 @@ You can also use [icons](/ui/components/icon) in the segmented control. To do th
<Example centered>
<nav class="nav nav-segmented" role="tablist">
<button class="nav-link active" role="tab" data-bs-toggle="tab" aria-selected="true" aria-current="page">
<Icon name="list" /> List
</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1">
<Icon name="layout" /> Kanban
</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1">
<Icon name="calendar" /> Calendar
</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1">
<Icon name="files" /> Files
</button>
<button class="nav-link active" role="tab" data-bs-toggle="tab" aria-selected="true" aria-current="page"><Icon name="list" /> List</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1"><Icon name="layout" /> Kanban</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1"><Icon name="calendar" /> Calendar</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1"><Icon name="files" /> Files</button>
</nav>
</Example>
@@ -99,18 +91,10 @@ The results can be seen in the example below.
<Example centered>
<nav class="nav nav-segmented nav-segmented-vertical" role="tablist">
<button class="nav-link active" role="tab" data-bs-toggle="tab" aria-selected="true" aria-current="page">
<Icon name="list" /> List
</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1">
<Icon name="layout" /> Kanban
</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1">
<Icon name="calendar" /> Calendar
</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1">
<Icon name="files" /> Files
</button>
<button class="nav-link active" role="tab" data-bs-toggle="tab" aria-selected="true" aria-current="page"><Icon name="list" /> List</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1"><Icon name="layout" /> Kanban</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1"><Icon name="calendar" /> Calendar</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1"><Icon name="files" /> Files</button>
</nav>
</Example>
@@ -126,16 +110,16 @@ The results can be seen in the examples below.
<Example centered vertical>
<nav class="nav nav-segmented nav-sm" role="tablist">
<button class="nav-link active" role="tab" data-bs-toggle="tab" aria-selected="true" aria-current="page"> List </button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1"> Kanban </button>
<button class="nav-link disabled" role="tab" data-bs-toggle="tab" aria-selected="false" aria-disabled="true" tabindex="-1"> Calendar </button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1"> Files </button>
<button class="nav-link active" role="tab" data-bs-toggle="tab" aria-selected="true" aria-current="page">List</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1">Kanban</button>
<button class="nav-link disabled" role="tab" data-bs-toggle="tab" aria-selected="false" aria-disabled="true" tabindex="-1">Calendar</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1">Files</button>
</nav>
<nav class="nav nav-segmented nav-lg" role="tablist">
<button class="nav-link active" role="tab" data-bs-toggle="tab" aria-selected="true" aria-current="page"> List </button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1"> Kanban </button>
<button class="nav-link disabled" role="tab" data-bs-toggle="tab" aria-selected="false" aria-disabled="true" tabindex="-1"> Calendar </button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1"> Files </button>
<button class="nav-link active" role="tab" data-bs-toggle="tab" aria-selected="true" aria-current="page">List</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1">Kanban</button>
<button class="nav-link disabled" role="tab" data-bs-toggle="tab" aria-selected="false" aria-disabled="true" tabindex="-1">Calendar</button>
<button class="nav-link" role="tab" data-bs-toggle="tab" aria-selected="false" tabindex="-1">Files</button>
</nav>
</Example>
+6 -16
View File
@@ -90,21 +90,11 @@ Use [buttons](/ui/components/button) with spinners to notify users that an actio
<Example centered>
<div class="btn-list">
<a href="#" class="btn btn-primary">
<span class="spinner-border spinner-border-sm me-2" role="status"></span> Button
</a>
<a href="#" class="btn btn-danger">
<span class="spinner-border spinner-border-sm me-2" role="status"></span> Button
</a>
<a href="#" class="btn btn-warning">
<span class="spinner-border spinner-border-sm me-2" role="status"></span> Button
</a>
<a href="#" class="btn btn-success">
<span class="spinner-border spinner-border-sm me-2" role="status"></span> Button
</a>
<a href="#" class="btn">
<span class="spinner-border spinner-border-sm me-2" role="status"></span> Button
</a>
<a href="#" class="btn btn-primary"> <span class="spinner-border spinner-border-sm me-2" role="status"></span> Button </a>
<a href="#" class="btn btn-danger"> <span class="spinner-border spinner-border-sm me-2" role="status"></span> Button </a>
<a href="#" class="btn btn-warning"> <span class="spinner-border spinner-border-sm me-2" role="status"></span> Button </a>
<a href="#" class="btn btn-success"> <span class="spinner-border spinner-border-sm me-2" role="status"></span> Button </a>
<a href="#" class="btn"> <span class="spinner-border spinner-border-sm me-2" role="status"></span> Button </a>
</div>
</Example>
@@ -113,7 +103,7 @@ Use [buttons](/ui/components/button) with spinners to notify users that an actio
Use animated dots to show the loading state of a component. They provide feedback for an action a user has taken, when it takes a bit longer to complete. To do it you need to use the `.animated-dots` class on a `span` element.
<Example centered>
<h1> Loading <span class="animated-dots"></span> </h1>
<h1>Loading <span class="animated-dots"></span></h1>
</Example>
Use buttons with animated dots to notify users that an action they have taken by clicking the button is in progress and prevent them from clicking multiple times or giving up.
+36 -108
View File
@@ -55,42 +55,18 @@ Use the default status to notify users about the status of a component or page,
You can add a dot to the status to make it more noticeable. To do this, use the `.status-dot` element inside the `.status` element.
<Example centered>
<span class="status status-blue">
<span class="status-dot"></span> Blue
</span>
<span class="status status-azure">
<span class="status-dot"></span> Azure
</span>
<span class="status status-indigo">
<span class="status-dot"></span> Indigo
</span>
<span class="status status-purple">
<span class="status-dot"></span> Purple
</span>
<span class="status status-pink">
<span class="status-dot"></span> Pink
</span>
<span class="status status-red">
<span class="status-dot"></span> Red
</span>
<span class="status status-orange">
<span class="status-dot"></span> Orange
</span>
<span class="status status-yellow">
<span class="status-dot"></span> Yellow
</span>
<span class="status status-lime">
<span class="status-dot"></span> Lime
</span>
<span class="status status-green">
<span class="status-dot"></span> Green
</span>
<span class="status status-teal">
<span class="status-dot"></span> Teal
</span>
<span class="status status-cyan">
<span class="status-dot"></span> Cyan
</span>
<span class="status status-blue"> <span class="status-dot"></span> Blue </span>
<span class="status status-azure"> <span class="status-dot"></span> Azure </span>
<span class="status status-indigo"> <span class="status-dot"></span> Indigo </span>
<span class="status status-purple"> <span class="status-dot"></span> Purple </span>
<span class="status status-pink"> <span class="status-dot"></span> Pink </span>
<span class="status status-red"> <span class="status-dot"></span> Red </span>
<span class="status status-orange"> <span class="status-dot"></span> Orange </span>
<span class="status status-yellow"> <span class="status-dot"></span> Yellow </span>
<span class="status status-lime"> <span class="status-dot"></span> Lime </span>
<span class="status status-green"> <span class="status-dot"></span> Green </span>
<span class="status status-teal"> <span class="status-dot"></span> Teal </span>
<span class="status status-cyan"> <span class="status-dot"></span> Cyan </span>
</Example>
### Animated dot
@@ -98,42 +74,18 @@ You can add a dot to the status to make it more noticeable. To do this, use the
You can also animate the dot to make it more noticeable. To do this, add a `.status-dot-animated` class to the `.status-dot` element.
<Example centered>
<span class="status status-blue">
<span class="status-dot status-dot-animated"></span> Blue
</span>
<span class="status status-azure">
<span class="status-dot status-dot-animated"></span> Azure
</span>
<span class="status status-indigo">
<span class="status-dot status-dot-animated"></span> Indigo
</span>
<span class="status status-purple">
<span class="status-dot status-dot-animated"></span> Purple
</span>
<span class="status status-pink">
<span class="status-dot status-dot-animated"></span> Pink
</span>
<span class="status status-red">
<span class="status-dot status-dot-animated"></span> Red
</span>
<span class="status status-orange">
<span class="status-dot status-dot-animated"></span> Orange
</span>
<span class="status status-yellow">
<span class="status-dot status-dot-animated"></span> Yellow
</span>
<span class="status status-lime">
<span class="status-dot status-dot-animated"></span> Lime
</span>
<span class="status status-green">
<span class="status-dot status-dot-animated"></span> Green
</span>
<span class="status status-teal">
<span class="status-dot status-dot-animated"></span> Teal
</span>
<span class="status status-cyan">
<span class="status-dot status-dot-animated"></span> Cyan
</span>
<span class="status status-blue"> <span class="status-dot status-dot-animated"></span> Blue </span>
<span class="status status-azure"> <span class="status-dot status-dot-animated"></span> Azure </span>
<span class="status status-indigo"> <span class="status-dot status-dot-animated"></span> Indigo </span>
<span class="status status-purple"> <span class="status-dot status-dot-animated"></span> Purple </span>
<span class="status status-pink"> <span class="status-dot status-dot-animated"></span> Pink </span>
<span class="status status-red"> <span class="status-dot status-dot-animated"></span> Red </span>
<span class="status status-orange"> <span class="status-dot status-dot-animated"></span> Orange </span>
<span class="status status-yellow"> <span class="status-dot status-dot-animated"></span> Yellow </span>
<span class="status status-lime"> <span class="status-dot status-dot-animated"></span> Lime </span>
<span class="status status-green"> <span class="status-dot status-dot-animated"></span> Green </span>
<span class="status status-teal"> <span class="status-dot status-dot-animated"></span> Teal </span>
<span class="status status-cyan"> <span class="status-dot status-dot-animated"></span> Cyan </span>
</Example>
## Lite status
@@ -141,42 +93,18 @@ You can also animate the dot to make it more noticeable. To do this, add a `.sta
Use the lite status to make the status less noticeable. To do this, add a `.status-lite` class to the `.status` element.
<Example centered>
<span class="status status-blue status-lite">
<span class="status-dot"></span> Blue
</span>
<span class="status status-azure status-lite">
<span class="status-dot"></span> Azure
</span>
<span class="status status-indigo status-lite">
<span class="status-dot"></span> Indigo
</span>
<span class="status status-purple status-lite">
<span class="status-dot"></span> Purple
</span>
<span class="status status-pink status-lite">
<span class="status-dot"></span> Pink
</span>
<span class="status status-red status-lite">
<span class="status-dot"></span> Red
</span>
<span class="status status-orange status-lite">
<span class="status-dot"></span> Orange
</span>
<span class="status status-yellow status-lite">
<span class="status-dot"></span> Yellow
</span>
<span class="status status-lime status-lite">
<span class="status-dot"></span> Lime
</span>
<span class="status status-green status-lite">
<span class="status-dot"></span> Green
</span>
<span class="status status-teal status-lite">
<span class="status-dot"></span> Teal
</span>
<span class="status status-cyan status-lite">
<span class="status-dot"></span> Cyan
</span>
<span class="status status-blue status-lite"> <span class="status-dot"></span> Blue </span>
<span class="status status-azure status-lite"> <span class="status-dot"></span> Azure </span>
<span class="status status-indigo status-lite"> <span class="status-dot"></span> Indigo </span>
<span class="status status-purple status-lite"> <span class="status-dot"></span> Purple </span>
<span class="status status-pink status-lite"> <span class="status-dot"></span> Pink </span>
<span class="status status-red status-lite"> <span class="status-dot"></span> Red </span>
<span class="status status-orange status-lite"> <span class="status-dot"></span> Orange </span>
<span class="status status-yellow status-lite"> <span class="status-dot"></span> Yellow </span>
<span class="status status-lime status-lite"> <span class="status-dot"></span> Lime </span>
<span class="status status-green status-lite"> <span class="status-dot"></span> Green </span>
<span class="status status-teal status-lite"> <span class="status-dot"></span> Teal </span>
<span class="status status-cyan status-lite"> <span class="status-dot"></span> Cyan </span>
</Example>
## Status dots
+15 -19
View File
@@ -48,11 +48,11 @@ Use tabs to let users access different sections within one context quickly and w
<div class="tab-content">
<div class="tab-pane active show" id="tabs-home-ex1">
<h4>Home tab</h4>
<div> Cursus turpis vestibulum, dui in pharetra vulputate id sed non turpis ultricies fringilla at sed facilisis lacus pellentesque purus nibh </div>
<div>Cursus turpis vestibulum, dui in pharetra vulputate id sed non turpis ultricies fringilla at sed facilisis lacus pellentesque purus nibh</div>
</div>
<div class="tab-pane" id="tabs-profile-ex1">
<h4>Profile tab</h4>
<div> Fringilla egestas nunc quis tellus diam rhoncus ultricies tristique enim at diam, sem nunc amet, pellentesque id egestas velit sed </div>
<div>Fringilla egestas nunc quis tellus diam rhoncus ultricies tristique enim at diam, sem nunc amet, pellentesque id egestas velit sed</div>
</div>
</div>
</div>
@@ -68,14 +68,10 @@ Add [icons](/ui/components/icon) to your tab labels, if you want to use a visual
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs" data-bs-toggle="tabs">
<li class="nav-item">
<a href="#tabs-home-ex2" class="nav-link active" data-bs-toggle="tab">
<Icon name="home" class="me-2" /> Home
</a>
<a href="#tabs-home-ex2" class="nav-link active" data-bs-toggle="tab"> <Icon name="home" class="me-2" /> Home </a>
</li>
<li class="nav-item">
<a href="#tabs-profile-ex2" class="nav-link" data-bs-toggle="tab">
<Icon name="user" class="me-2" /> Profile
</a>
<a href="#tabs-profile-ex2" class="nav-link" data-bs-toggle="tab"> <Icon name="user" class="me-2" /> Profile </a>
</li>
<li class="nav-item ms-auto">
<a href="#tabs-settings-ex2" class="nav-link" title="Settings" data-bs-toggle="tab">
@@ -92,15 +88,15 @@ Add [icons](/ui/components/icon) to your tab labels, if you want to use a visual
<div class="tab-content">
<div class="tab-pane active show" id="tabs-home-ex2">
<h4>Home tab</h4>
<div> Cursus turpis vestibulum, dui in pharetra vulputate id sed non turpis ultricies fringilla at sed facilisis lacus pellentesque purus nibh </div>
<div>Cursus turpis vestibulum, dui in pharetra vulputate id sed non turpis ultricies fringilla at sed facilisis lacus pellentesque purus nibh</div>
</div>
<div class="tab-pane" id="tabs-profile-ex2">
<h4>Profile tab</h4>
<div> Fringilla egestas nunc quis tellus diam rhoncus ultricies tristique enim at diam, sem nunc amet, pellentesque id egestas velit sed </div>
<div>Fringilla egestas nunc quis tellus diam rhoncus ultricies tristique enim at diam, sem nunc amet, pellentesque id egestas velit sed</div>
</div>
<div class="tab-pane" id="tabs-settings-ex2">
<h4>Settings tab</h4>
<div> Donec ac vitae diam amet vel leo egestas consequat rhoncus in luctus amet, facilisi sit mauris accumsan nibh habitant senectus </div>
<div>Donec ac vitae diam amet vel leo egestas consequat rhoncus in luctus amet, facilisi sit mauris accumsan nibh habitant senectus</div>
</div>
</div>
</div>
@@ -134,15 +130,15 @@ Use tabs without label names to save space and make the tab content easy to reco
<div class="tab-content">
<div class="tab-pane active show" id="tabs-home-ex3">
<h4>Home tab</h4>
<div> Cursus turpis vestibulum, dui in pharetra vulputate id sed non turpis ultricies fringilla at sed facilisis lacus pellentesque purus nibh </div>
<div>Cursus turpis vestibulum, dui in pharetra vulputate id sed non turpis ultricies fringilla at sed facilisis lacus pellentesque purus nibh</div>
</div>
<div class="tab-pane" id="tabs-profile-ex3">
<h4>Profile tab</h4>
<div> Fringilla egestas nunc quis tellus diam rhoncus ultricies tristique enim at diam, sem nunc amet, pellentesque id egestas velit sed </div>
<div>Fringilla egestas nunc quis tellus diam rhoncus ultricies tristique enim at diam, sem nunc amet, pellentesque id egestas velit sed</div>
</div>
<div class="tab-pane" id="tabs-settings-ex3">
<h4>Settings tab</h4>
<div> Donec ac vitae diam amet vel leo egestas consequat rhoncus in luctus amet, facilisi sit mauris accumsan nibh habitant senectus </div>
<div>Donec ac vitae diam amet vel leo egestas consequat rhoncus in luctus amet, facilisi sit mauris accumsan nibh habitant senectus</div>
</div>
</div>
</div>
@@ -176,11 +172,11 @@ Make one or more of your tabs into a [dropdown](/ui/components/dropdown) to add
<div class="tab-content">
<div class="tab-pane active show" id="tabs-home-ex4">
<h4>Home tab</h4>
<div> Cursus turpis vestibulum, dui in pharetra vulputate id sed non turpis ultricies fringilla at sed facilisis lacus pellentesque purus nibh </div>
<div>Cursus turpis vestibulum, dui in pharetra vulputate id sed non turpis ultricies fringilla at sed facilisis lacus pellentesque purus nibh</div>
</div>
<div class="tab-pane" id="tabs-profile-ex4">
<h4>Profile tab</h4>
<div> Fringilla egestas nunc quis tellus diam rhoncus ultricies tristique enim at diam, sem nunc amet, pellentesque id egestas velit sed </div>
<div>Fringilla egestas nunc quis tellus diam rhoncus ultricies tristique enim at diam, sem nunc amet, pellentesque id egestas velit sed</div>
</div>
</div>
</div>
@@ -214,15 +210,15 @@ Add the `.nav-fill` class to make the tabs take up the full space of the parent
<div class="tab-content">
<div class="tab-pane active show" id="tabs-home-ex5">
<h4>Home tab</h4>
<div> Cursus turpis vestibulum, dui in pharetra vulputate id sed non turpis ultricies fringilla at sed facilisis lacus pellentesque purus nibh </div>
<div>Cursus turpis vestibulum, dui in pharetra vulputate id sed non turpis ultricies fringilla at sed facilisis lacus pellentesque purus nibh</div>
</div>
<div class="tab-pane" id="tabs-profile-ex5">
<h4>Profile tab</h4>
<div> Fringilla egestas nunc quis tellus diam rhoncus ultricies tristique enim at diam, sem nunc amet, pellentesque id egestas velit sed </div>
<div>Fringilla egestas nunc quis tellus diam rhoncus ultricies tristique enim at diam, sem nunc amet, pellentesque id egestas velit sed</div>
</div>
<div class="tab-pane" id="tabs-activity-ex5">
<h4>Activity tab</h4>
<div> Donec ac vitae diam amet vel leo egestas consequat rhoncus in luctus amet, facilisi sit mauris accumsan nibh habitant senectus </div>
<div>Donec ac vitae diam amet vel leo egestas consequat rhoncus in luctus amet, facilisi sit mauris accumsan nibh habitant senectus</div>
</div>
</div>
</div>
+7 -7
View File
@@ -71,7 +71,7 @@ The `.table` class adds basic styling to a table:
<td>Paweł Kuna</td>
<td class="text-secondary">UI Designer, Training</td>
<td class="text-secondary">
<a href="#" class="text-reset">paweluna@howstuffworks.com</a>
<a href="#" class="text-reset">paweluna&#64;howstuffworks.com</a>
</td>
<td class="text-secondary">User</td>
<td><a href="#">Edit</a></td>
@@ -79,28 +79,28 @@ The `.table` class adds basic styling to a table:
<tr>
<td>Jeffie Lewzey</td>
<td class="text-secondary">Chemical Engineer, Support</td>
<td class="text-secondary"><a href="#" class="text-reset">jlewzey1@seesaa.net</a></td>
<td class="text-secondary"><a href="#" class="text-reset">jlewzey1&#64;seesaa.net</a></td>
<td class="text-secondary">Admin</td>
<td><a href="#">Edit</a></td>
</tr>
<tr>
<td>Mallory Hulme</td>
<td class="text-secondary">Geologist IV, Support</td>
<td class="text-secondary"><a href="#" class="text-reset">mhulme2@domainmarket.com</a></td>
<td class="text-secondary"><a href="#" class="text-reset">mhulme2&#64;domainmarket.com</a></td>
<td class="text-secondary">User</td>
<td><a href="#">Edit</a></td>
</tr>
<tr>
<td>Dunn Slane</td>
<td class="text-secondary">Research Nurse, Sales</td>
<td class="text-secondary"><a href="#" class="text-reset">dslane3@epa.gov</a></td>
<td class="text-secondary"><a href="#" class="text-reset">dslane3&#64;epa.gov</a></td>
<td class="text-secondary">Owner</td>
<td><a href="#">Edit</a></td>
</tr>
<tr>
<td>Emmy Levet</td>
<td class="text-secondary">VP Product Management, Accounting</td>
<td class="text-secondary"><a href="#" class="text-reset">elevet4@senate.gov</a></td>
<td class="text-secondary"><a href="#" class="text-reset">elevet4&#64;senate.gov</a></td>
<td class="text-secondary">Admin</td>
<td><a href="#">Edit</a></td>
</tr>
@@ -183,7 +183,7 @@ If you don't want the table cell content to wrap to another line, use the `table
<td>Paweł Kuna</td>
<td class="text-secondary">UI Designer, Training</td>
<td class="text-secondary">
<a href="#" class="text-reset">paweluna@howstuffworks.com</a>
<a href="#" class="text-reset">paweluna&#64;howstuffworks.com</a>
</td>
<td class="text-secondary">User</td>
<td> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi, commodi cupiditate debitis deserunt expedita hic incidunt iste modi molestiae nesciunt non nostrum perferendis perspiciatis placeat praesentium quaerat quo repellendus, voluptates. </td>
@@ -509,7 +509,7 @@ A long value in one column stretches the whole table. Put `td-truncate` on that
<tr>
<td>Elliot Fisher</td>
<td class="td-truncate">
<div class="text-truncate">A note long enough that it would otherwise push the first column out of the way and stretch the table past the width of the page.</div>
<div class="text-truncate"> A note long enough that it would otherwise push the first column out of the way and stretch the table past the width of the page. </div>
</td>
</tr>
</tbody>
+5 -5
View File
@@ -23,10 +23,10 @@ Use the default markup to create tooltips that will help users understand partic
<Example>
<div class="btn-list">
<button type="button" class="btn" data-bs-toggle="tooltip" data-bs-placement="top" title="Tooltip on top"> Tooltip on top </button>
<button type="button" class="btn" data-bs-toggle="tooltip" data-bs-placement="right" title="Tooltip on right"> Tooltip on right </button>
<button type="button" class="btn" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Tooltip on bottom"> Tooltip on bottom </button>
<button type="button" class="btn" data-bs-toggle="tooltip" data-bs-placement="left" title="Tooltip on left"> Tooltip on left </button>
<button type="button" class="btn" data-bs-toggle="tooltip" data-bs-placement="top" title="Tooltip on top">Tooltip on top</button>
<button type="button" class="btn" data-bs-toggle="tooltip" data-bs-placement="right" title="Tooltip on right">Tooltip on right</button>
<button type="button" class="btn" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Tooltip on bottom">Tooltip on bottom</button>
<button type="button" class="btn" data-bs-toggle="tooltip" data-bs-placement="left" title="Tooltip on left">Tooltip on left</button>
</div>
</Example>
@@ -35,7 +35,7 @@ Use the default markup to create tooltips that will help users understand partic
If the default tooltip is not enough, you can add the option to use HTML code in the text to highlight particular bits of information and make the content more attractive.
<Example>
<button type="button" class="btn" data-bs-toggle="tooltip" data-bs-html="true" title="<em>Tooltip</em> <u>with</u> <b>HTML</b>"> Tooltip with HTML </button>
<button type="button" class="btn" data-bs-toggle="tooltip" data-bs-html="true" title="<em>Tooltip</em> <u>with</u> <b>HTML</b>">Tooltip with HTML</button>
</Example>
## JavaScript
+3 -9
View File
@@ -148,21 +148,15 @@ You can also add text to the element. Look at the example below to see how it wo
<div class="form-selectgroup">
<label class="form-selectgroup-item">
<input type="radio" name="icons" value="home" class="form-selectgroup-input" checked />
<span class="form-selectgroup-label">
<Icon name="home" class="me-1" /> Home
</span>
<span class="form-selectgroup-label"> <Icon name="home" class="me-1" /> Home </span>
</label>
<label class="form-selectgroup-item">
<input type="radio" name="icons" value="user" class="form-selectgroup-input" />
<span class="form-selectgroup-label">
<Icon name="user" class="me-1" /> User
</span>
<span class="form-selectgroup-label"> <Icon name="user" class="me-1" /> User </span>
</label>
<label class="form-selectgroup-item">
<input type="radio" name="icons" value="circle" class="form-selectgroup-input" />
<span class="form-selectgroup-label">
<Icon name="circle" class="me-1" /> Circle
</span>
<span class="form-selectgroup-label"> <Icon name="circle" class="me-1" /> Circle </span>
</label>
<label class="form-selectgroup-item">
<input type="radio" name="icons" value="square" class="form-selectgroup-input" />
@@ -60,7 +60,7 @@ You can set any of the five keys in three ways, and they all agree with each oth
1. **URL query parameter** - add `?theme=dark` (or any other key) to the page URL. The script reads it, applies it, and saves it to `localStorage` so it persists on the next visit.
2. **`localStorage`** - each key is stored under `tabler-<key>`, for example `tabler-theme` or `tabler-theme-primary`. This is what makes the choice persist across page loads once it has been set once, by either method here.
3. **Server-rendered attribute** - if you already know the visitor's preference (from a cookie or account setting), you can render `data-bs-*` attributes on `<html>` yourself. The script still runs and may override them from `localStorage` or the URL, so treat this as an initial value rather than a hard override.
3. **Server-rendered attribute** - if you already know the visitor's preference (from a cookie or account setting), render the `data-bs-*` attributes on `<html>` yourself. The script keeps them as the starting value and only replaces them with a stored choice or a URL parameter.
A query parameter always wins over what's already stored, and updates the stored value for next time:
+6 -1
View File
@@ -47,7 +47,12 @@ Every docs page also links to these files in its `<head>`, so a crawler that alr
```html
<link rel="alternate" type="text/plain" href="/llms.txt" title="llms.txt" />
<link rel="alternate" type="text/markdown" href="/ui/components/button.md" title="Button (markdown)" />
<link
rel="alternate"
type="text/markdown"
href="/ui/components/button.md"
title="Button (markdown)"
/>
```
## Usage
+4 -4
View File
@@ -115,13 +115,13 @@ The built-in `@hotwired/turbo` integration is gone, together with the `.turbo-pr
`tabler-theme.js` used `light` as its default color mode. It now defaults to `auto`: with no stored choice, the script reads `prefers-color-scheme` and sets `data-bs-theme` on `<html>` to `light` or `dark`. Visitors whose operating system is set to dark will see your pages in dark mode after the upgrade.
The script applies the resolved mode on every load, so a `data-bs-theme` you render on the server is replaced. If your pages should stay light no matter what the OS says, do not load the script: the CSS renders in light mode whenever `data-bs-theme` is absent.
To keep light as the starting point, render the attribute on the server; the script only replaces it with a stored choice or a URL parameter:
```diff
- <script src="/dist/js/tabler-theme.min.js"></script>
```html
<html data-bs-theme="light">
```
If you keep the script, a visitor can pick a mode with `?theme=light` or `?theme=dark` in the URL, and the choice is stored in `localStorage`. See [Color modes](/ui/getting-started/color-modes) for all theme settings.
A visitor can still pick a mode with `?theme=light` or `?theme=dark` in the URL, and the choice is stored in `localStorage`. See [Color modes](/ui/getting-started/color-modes) for all theme settings.
## Removed payment providers
+46 -26
View File
@@ -32,32 +32,34 @@ The navbar can contain links, buttons, and other elements. You can customize the
<span class="navbar-toggler-icon"></span>
</button>
<Logo class="me-3" />
<ul class="navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="#">
<span class="nav-link-icon">
<Icon name="home" />
</span>
<span class="nav-link-title">Home</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<span class="nav-link-icon">
<Icon name="checkbox" />
</span>
<span class="nav-link-title">Profile</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<span class="nav-link-icon">
<Icon name="checkbox" />
</span>
<span class="nav-link-title">Settings</span>
</a>
</li>
</ul>
<div class="collapse navbar-collapse" id="navbar-menu">
<ul class="navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="#">
<span class="nav-link-icon">
<Icon name="home" />
</span>
<span class="nav-link-title">Home</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<span class="nav-link-icon">
<Icon name="checkbox" />
</span>
<span class="nav-link-title">Profile</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<span class="nav-link-icon">
<Icon name="checkbox" />
</span>
<span class="nav-link-title">Settings</span>
</a>
</li>
</ul>
</div>
<div class="navbar-nav flex-row order-md-last ms-auto">
<div class="nav-item dropdown">
<a href="#" class="nav-link d-flex lh-1 text-reset" data-bs-toggle="dropdown" aria-label="Open user menu">
@@ -81,6 +83,24 @@ The navbar can contain links, buttons, and other elements. You can customize the
</header>
</Example>
## Language selector
Add a language selector to the navbar as a dropdown. The toggle shows the current language code and the menu lists the languages with a `data-lang` attribute, so a script can switch the locale. Give the toggle an `aria-label`, because the code alone does not say what the control does.
<Example>
<div class="nav-item dropdown">
<a href="#" class="nav-link px-0" data-bs-toggle="dropdown" aria-label="Select language" data-bs-auto-close="outside" aria-expanded="false">EN</a>
<div class="dropdown-menu dropdown-menu-arrow">
<a class="dropdown-item" href="#" data-lang="cs">Čeština</a>
<a class="dropdown-item" href="#" data-lang="de">Deutsch</a>
<a class="dropdown-item" href="#" data-lang="en">English</a>
<a class="dropdown-item" href="#" data-lang="pl">Polski</a>
</div>
</div>
</Example>
The preview navbar renders it from `shared/data/languages.json` with the `showLanguageSelector` prop of `NavbarSide`.
## Accessibility
- Wrap each navigation region in `nav` and give it an `aria-label`, so a sidebar and a top bar can be told apart.
+6 -15
View File
@@ -21,10 +21,8 @@ Use the `.page-header` class to combine a pretitle, a page title, and action but
<div class="col-auto ms-auto">
<div class="btn-list">
<span class="d-none d-sm-inline"><a href="#" class="btn">New view</a></span>
<a href="#" class="btn btn-primary d-none d-sm-inline-block" data-bs-toggle="modal" data-bs-target="#modal-report">
<Icon name="plus" /> Create new report
</a>
<a href="#" class="btn btn-primary d-sm-none btn-icon" data-bs-toggle="modal" data-bs-target="#modal-report" aria-label="Create new report">
<a href="#" class="btn btn-primary d-none d-sm-inline-block"> <Icon name="plus" /> Create new report </a>
<a href="#" class="btn btn-primary d-sm-none btn-icon" aria-label="Create new report">
<Icon name="plus" />
</a>
</div>
@@ -55,9 +53,7 @@ Add an [avatar](/ui/components/avatar) and a `.page-subtitle` block with meta in
<Icon name="users" />
<a href="#" class="text-reset">194 friends</a>
</div>
<div class="col-auto text-success">
<Icon name="check" /> Verified
</div>
<div class="col-auto text-success"><Icon name="check" /> Verified</div>
</div>
</div>
</div>
@@ -89,9 +85,7 @@ Combine the page title with a result count, a search input, and a primary action
<span class="input-icon-addon"><Icon name="search" /></span>
</div>
</div>
<a href="#" class="btn btn-primary">
<Icon name="plus" /> Add photo
</a>
<a href="#" class="btn btn-primary"> <Icon name="plus" /> Add photo </a>
</div>
</div>
</div>
@@ -109,8 +103,7 @@ A page header with a border to separate content is an effective way to organize
<div class="col">
<h2 class="page-title">Improve cards with no border</h2>
<div class="text-secondary mt-1">
<a href="#" class="text-reset">#693</a> opened by
<a href="#" class="text-body">Jeffie Lewzey</a> in
<a href="#" class="text-reset">#693</a> opened by <a href="#" class="text-body">Jeffie Lewzey</a> in
<a href="#" class="text-body">Calendar Page</a>
</div>
</div>
@@ -119,9 +112,7 @@ A page header with a border to separate content is an effective way to organize
<a href="#" class="btn">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M7 7h-1a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-1" /> <path d="M20.385 6.585a2.1 2.1 0 0 0 -2.97 -2.97l-8.415 8.385v3h3l8.385 -8.415z" /> <path d="M16 5l3 3" /> </svg> Edit
</a>
<a href="#" class="btn d-none d-md-inline-flex">
<Icon name="bell" /> Subscribe
</a>
<a href="#" class="btn d-none d-md-inline-flex"> <Icon name="bell" /> Subscribe </a>
</div>
</div>
</div>
+15 -11
View File
@@ -20,7 +20,7 @@ To create a sample version of the dashboard, you can use the following code snip
<div class="page">
<header class="navbar navbar-expand-sm d-print-none">
<div class="container-xl">
<h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
<h1 class="navbar-brand navbar-brand-autodark pe-0 pe-md-3">
<a href="#">
<img src="/static/logo.svg" width="110" height="32" alt="Tabler" class="navbar-brand-image" />
</a>
@@ -39,7 +39,7 @@ To create a sample version of the dashboard, you can use the following code snip
</div>
</header>
<div class="page-wrapper">
<main id="content" class="page-body">
<main class="page-body">
<div class="container-xl">
<div class="row row-deck row-cards">
<div class="col-4">
@@ -86,7 +86,7 @@ Add a `footer` element with the `footer` class after `.page-wrapper` to close th
</div>
<div class="col-12 col-lg-auto mt-3 mt-lg-0">
<ul class="list-inline list-inline-dots mb-0">
<li class="list-inline-item"> Copyright &copy; 2026 <a href="#" class="link-secondary">Your company</a> . All rights reserved. </li>
<li class="list-inline-item">Copyright &copy; 2026 <a href="#" class="link-secondary">Your company</a> . All rights reserved.</li>
<li class="list-inline-item"><a href="#" class="link-secondary">v1.0.0</a></li>
</ul>
</div>
@@ -149,7 +149,7 @@ To create a sidebar layout, you can use the following code snippet. This code sn
</div>
</div>
</div>
<main id="content" class="page-body">
<main class="page-body">
<div class="container-xl">
<div class="row row-deck row-cards">
<div class="col-sm-6 col-lg-3">
@@ -305,7 +305,7 @@ Use `data-bs-auto-close="outside"` on submenu toggles in a folded sidebar, so an
</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#sidebar-folded-reports" data-bs-toggle="dropdown" data-bs-auto-close="outside" role="button" aria-haspopup="true" aria-expanded="false">
<a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown" data-bs-auto-close="outside" role="button" aria-haspopup="true" aria-expanded="false">
<span class="nav-link-icon"><Icon name="chart-bar" /></span>
<span class="nav-link-title">Reports</span>
</a>
@@ -326,7 +326,7 @@ Use `data-bs-auto-close="outside"` on submenu toggles in a folded sidebar, so an
</div>
</aside>
<div class="page-wrapper">
<main id="content" class="page-body">
<main class="page-body">
<div class="container-xl">
<div class="row row-deck row-cards">
<div class="col-6">
@@ -355,9 +355,7 @@ A folded rail does not scroll. If your menu has more items than the screen can f
Use `navbar-folded-hover` instead of `navbar-folded` to get a folded rail that unfolds when the user hovers over it or moves keyboard focus into it. The expanded sidebar overlays the page content, so nothing moves around. This variant also restores scrolling of long menus while the sidebar is unfolded.
```html
<aside class="navbar navbar-vertical navbar-expand-lg navbar-folded-hover">
...
</aside>
<aside class="navbar navbar-vertical navbar-expand-lg navbar-folded-hover">...</aside>
```
### Pin button and saved state
@@ -369,7 +367,13 @@ To let users fold and unfold the sidebar at runtime, add a button with `data-bs-
<a href="." aria-label="Tabler">
<svg class="navbar-brand-image"><!-- logo mark --></svg>
</a>
<button type="button" class="btn btn-action btn-sm" data-bs-toggle="sidebar-folded" aria-pressed="false" aria-label="Pin sidebar">
<button
type="button"
class="btn btn-action btn-sm"
data-bs-toggle="sidebar-folded"
aria-pressed="false"
aria-label="Pin sidebar"
>
<svg><!-- pin icon --></svg>
</button>
</div>
@@ -401,7 +405,7 @@ A layout is what a screen reader user navigates by, so the landmarks matter more
```html
<a href="#content" class="visually-hidden-focusable skip-link">Skip to main content</a>
...
<main id="content" class="page-body">
<main class="page-body">
```
`visually-hidden-focusable` keeps the link out of the way until it takes focus, and then shows it.
+7 -7
View File
@@ -139,18 +139,18 @@ An option can carry extra markup — an avatar, a flag, a badge — through a `d
<Example>
<select class="form-select" id="select-avatar" data-placeholder="Assign to…">
<option value="1" data-custom-properties='<span class="avatar avatar-xs">JD</span>'> Jane Doe </option>
<option value="2" data-custom-properties='<span class="avatar avatar-xs">MS</span>'> Mark Smith </option>
<option value="3" data-custom-properties='<span class="avatar avatar-xs">AK</span>'> Amy Kim </option>
<option value="1" data-custom-properties='<span class="avatar avatar-xs">JD</span>'>Jane Doe</option>
<option value="2" data-custom-properties='<span class="avatar avatar-xs">MS</span>'>Mark Smith</option>
<option value="3" data-custom-properties='<span class="avatar avatar-xs">AK</span>'>Amy Kim</option>
</select>
</Example>
<Example>
<select class="form-select" id="select-flag" data-placeholder="Pick a country">
<option value="us" data-custom-properties='<span class="flag flag-xs flag-country-us"></span>'> United States </option>
<option value="gb" data-custom-properties='<span class="flag flag-xs flag-country-gb"></span>'> United Kingdom </option>
<option value="de" data-custom-properties='<span class="flag flag-xs flag-country-de"></span>'> Germany </option>
<option value="fr" data-custom-properties='<span class="flag flag-xs flag-country-fr"></span>'> France </option>
<option value="us" data-custom-properties='<span class="flag flag-xs flag-country-us"></span>'>United States</option>
<option value="gb" data-custom-properties='<span class="flag flag-xs flag-country-gb"></span>'>United Kingdom</option>
<option value="de" data-custom-properties='<span class="flag flag-xs flag-country-de"></span>'>Germany</option>
<option value="fr" data-custom-properties='<span class="flag flag-xs flag-country-fr"></span>'>France</option>
</select>
</Example>
+2 -4
View File
@@ -16,8 +16,7 @@ import { site } from '@shared/lib/site.ts';
A textarea keeps its height no matter how much text it holds, so long answers end up in a small scrolling box. The [autosize](https://github.com/jackmoore/autosize) plugin grows the field as the user types and shrinks it again when text is removed.
<Example column vertical>
<label class="form-label">Autosize example</label>
<textarea class="form-control" data-bs-toggle="autosize" placeholder="Type something…"></textarea>
<label class="form-label">Autosize example</label> <textarea class="form-control" data-bs-toggle="autosize" placeholder="Type something…"></textarea>
</Example>
## Installation
@@ -37,8 +36,7 @@ The plugin only changes the height of the field, so it needs no CSS of its own.
Add `data-bs-toggle="autosize"` to a textarea. Tabler picks it up on page load, so there is nothing else to call.
<Example column vertical>
<label class="form-label">Message</label>
<textarea class="form-control" data-bs-toggle="autosize" rows="1" placeholder="Write a message…"></textarea>
<label class="form-label">Message</label> <textarea class="form-control" data-bs-toggle="autosize" rows="1" placeholder="Write a message…"></textarea>
</Example>
Set `rows` to choose the starting height. With `rows="1"` the field starts as a single line and grows from there.
+13 -8
View File
@@ -81,11 +81,11 @@ The size classes below reserve space before the chart renders, so the layout doe
| `chart-square` | 5.75rem, for a pie or radial chart next to text |
<Example>
<div class="card">
<div class="card-body">
<Chart chartId="demo-line" id="demo-line-sm" size="sm" label="Small chart example" />
</div>
<div class="card">
<div class="card-body">
<Chart chartId="demo-line" id="demo-line-sm" size="sm" label="Small chart example" />
</div>
</div>
</Example>
### Sparklines
@@ -93,9 +93,9 @@ The size classes below reserve space before the chart renders, so the layout doe
A sparkline is a small chart with no axes, grid or tooltip, meant to sit inline next to a number. Use the `chart-sparkline` class and turn on the ApexCharts `sparkline` option.
<Example centered>
<ChartSparkline id="docs-sparkline-bar" type="bar" data="4,9,6,10,7,12,8" />
<ChartSparkline id="docs-sparkline-line" type="line" color="green" data="4,9,6,10,7,12,8" />
<ChartSparkline id="docs-sparkline-donut" type="donut" color="azure" percentage="68" />
<ChartSparkline id="docs-sparkline-bar" type="bar" data="4,9,6,10,7,12,8" />
<ChartSparkline id="docs-sparkline-line" type="line" color="green" data="4,9,6,10,7,12,8" />
<ChartSparkline id="docs-sparkline-donut" type="donut" color="azure" percentage="68" />
</Example>
Add `chart-sparkline-sm` for a shorter sparkline, `chart-sparkline-square` for a pie or radial one, and `chart-sparkline-wide` when the default width is too narrow.
@@ -402,7 +402,12 @@ A chart is a picture, and a screen reader reads the SVG inside it as a pile of n
- Charts animate on first render. Set `chart.animations.enabled: false` when the page respects `prefers-reduced-motion`.
```html
<div id="chart-revenue" class="chart" role="img" aria-label="Monthly revenue, January to June 2026"></div>
<div
id="chart-revenue"
class="chart"
role="img"
aria-label="Monthly revenue, January to June 2026"
></div>
```
## SCSS variables
@@ -0,0 +1,39 @@
---
title: Background blur
summary: Blur whatever sits behind an element with the bg-blur utility. It works with translucent backgrounds, so overlays, sticky bars and floating cards stay readable on top of busy content.
description: Use the bg-blur utility to add a backdrop blur behind translucent overlays, sticky bars and floating cards.
related: [/ui/utilities/background-patterns, /ui/components/card]
added-in: 1.5.0
---
import Example from '@components/Example.astro';
## Overview
The `bg-blur` class sets `backdrop-filter: blur(4px)` on an element. The blur applies to the content behind the element, not to the element itself, so pair it with a translucent background such as `bg-white-lt` or an `rgba()` color. On an opaque background the blur has nothing to show through.
## Usage
Place the blurred element over an image or a pattern. The text stays sharp while the background softens.
<Example>
<div class="bg-pattern-dots bg-primary-lt rounded p-4">
<div class="card bg-blur" style="background-color: rgba(255, 255, 255, 0.6)">
<div class="card-body">
<h3 class="card-title">Floating card</h3>
<p class="mb-0">The dots behind this card are blurred by <code>bg-blur</code> .</p>
</div>
</div>
</div>
</Example>
A sticky navbar or a footer bar over scrolling content is the same pattern: keep the bar translucent and add `bg-blur`.
```html
<header class="navbar sticky-top bg-blur" style="background-color: rgba(255, 255, 255, 0.8)">
...
</header>
```
## Accessibility
The blur changes only the backdrop. Check the text against the resulting background: a translucent surface over a dark photo can drop below the 4.5:1 contrast ratio even though the same surface passes on a light one. Raise the alpha of the background color when it does.
+33 -33
View File
@@ -21,13 +21,13 @@ Borders can be applied to specific sides of an element using utility classes. Th
```
<Example centered hideCode>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border"> 1 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border-top"> 2 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border-end"> 3 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border-bottom"> 4 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border-start"> 5 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border-x"> 6 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border-y"> 7 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border">1</div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border-top">2</div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border-end">3</div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border-bottom">4</div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border-start">5</div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border-x">6</div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border-y">7</div>
</Example>
@@ -42,9 +42,9 @@ Border size utilities allow you to control the thickness of borders, providing f
```
<Example centered hideCode>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border-0"> 1 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border"> 2 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border-wide"> 3 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border-0">1</div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border">2</div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border-wide">3</div>
</Example>
## Remove border
@@ -61,12 +61,12 @@ If you want to selectively remove borders from specific sides of an element, you
```
<Example centered hideCode>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-top-0"> 1 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-end-0"> 2 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-bottom-0"> 3 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-start-0"> 4 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-x-0"> 5 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-y-0"> 6 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-top-0">1</div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-end-0">2</div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-bottom-0">3</div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-start-0">4</div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-x-0">5</div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-y-0">6</div>
</Example>
@@ -86,14 +86,14 @@ Customizing the border color helps to enhance the visual appeal and consistency
```
<Example centered hideCode>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-primary"> 1 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-secondary"> 2 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-success"> 3 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-warning"> 4 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-danger"> 5 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-info"> 6 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-dark"> 7 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-light"> 8 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-primary">1</div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-secondary">2</div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-success">3</div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-warning">4</div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-danger">5</div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-info">6</div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-dark">7</div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border border-light">8</div>
</Example>
## Border radius
@@ -110,12 +110,12 @@ Adding border radius gives elements smooth, rounded edges, which can make design
```
<Example centered hideCode>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border rounded-0"> 1 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border rounded"> 2 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border rounded-1"> 3 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border rounded-2"> 4 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border rounded-3"> 5 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border rounded-circle"> 6 </div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border rounded-0">1</div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border rounded">2</div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border rounded-1">3</div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border rounded-2">4</div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border rounded-3">5</div>
<div class="d-flex align-items-center justify-content-center text-secondary w-6 h-6 bg-light border rounded-circle">6</div>
</Example>
@@ -129,9 +129,9 @@ You can adjust the opacity of borders to create subtle visual effects or to blen
<Example hideCode>
<div class="border border-success p-2 mb-2">This is default success border</div>
<div class="border border-success p-2 mb-2 border-opacity-75"> This is 75% opacity success border </div>
<div class="border border-success p-2 mb-2 border-opacity-50"> This is 50% opacity success border </div>
<div class="border border-success p-2 mb-2 border-opacity-25"> This is 25% opacity success border </div>
<div class="border border-success p-2 mb-2 border-opacity-75">This is 75% opacity success border</div>
<div class="border border-success p-2 mb-2 border-opacity-50">This is 50% opacity success border</div>
<div class="border border-success p-2 mb-2 border-opacity-25">This is 25% opacity success border</div>
<div class="border border-success p-2 border-opacity-10">This is 10% opacity success border</div>
</Example>
+1 -1
View File
@@ -107,7 +107,7 @@ Here is a list of classes:
<Example codeOnly>
<div class="bg-gradient bg-gradient-from-purple">...</div>
<div class="bg-gradient bg-gradient-from-pink bg-gradient-via-orange bg-gradient-to-yellow"> ... </div>
<div class="bg-gradient bg-gradient-from-pink bg-gradient-via-orange bg-gradient-to-yellow">...</div>
</Example>
## Available colors
+2 -6
View File
@@ -21,12 +21,8 @@ Change the way in which the content is selected when the user interacts with it.
Tabler provides `.pe-none` and `.pe-auto` classes to prevent or add element interactions.
<Example>
<div>
<a href="#" class="pe-none" tabindex="-1" aria-disabled="true">This link</a> can not be clicked.
</div>
<div>
<a href="#" class="pe-auto">This link</a> can be clicked (this is default behavior).
</div>
<div><a href="#" class="pe-none" tabindex="-1" aria-disabled="true">This link</a> can not be clicked.</div>
<div><a href="#" class="pe-auto">This link</a> can be clicked (this is default behavior).</div>
</Example>
## Accessibility
+8 -14
View File
@@ -24,7 +24,7 @@ interface Props {
title: string;
/** front matter: optional SEO title override */
seoTitle?: string | undefined;
/** front matter: lead below the title (p.text-secondary.fs-3.lh-3) */
/** front matter: lead below the title (p.text-secondary.fs-3.lh-lg) */
summary?: string | undefined;
/** repo-relative path of the file implementing the component, linked next to the edit link */
sourcePath?: string | undefined;
@@ -288,14 +288,8 @@ const relatedPages = await Promise.all(
{pagePlugins.map((plugin) => <link href={`/dist/css/tabler-${plugin}.css`} rel="stylesheet" />)}
<!-- END PLUGINS STYLES -->
<!-- BEGIN CUSTOM FONT -->
<link rel="preconnect" href="https://rsms.me/" />
<link rel="preconnect" href="https://rsms.me/" crossorigin />
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
<!-- END CUSTOM FONT -->
<!-- BEGIN DOCS STYLES -->
<link rel="stylesheet" href="/css/docs.css" />
<link rel="stylesheet" href={environment === 'development' ? '/css/docs.css' : '/css/docs.min.css'} />
<!-- END DOCS STYLES -->
</head>
@@ -371,10 +365,10 @@ const relatedPages = await Promise.all(
)}
<Prose
data-bs-spy="scroll"
data-bs-target="#toc"
data-bs-root-margin="50px 0px -0%"
data-bs-smooth-scroll="true"
data-bs-spy={toc.length > 0 ? 'scroll' : undefined}
data-bs-target={toc.length > 0 ? '#toc' : undefined}
data-bs-root-margin={toc.length > 0 ? '50px 0px -0%' : undefined}
data-bs-smooth-scroll={toc.length > 0 ? 'true' : undefined}
tabindex="0"
>
<div class="d-flex">
@@ -385,13 +379,13 @@ const relatedPages = await Promise.all(
{
addedIn && (
<div class="ms-auto">
<span class="badge bg-primary-lt text-primary-lt-fg">Added in {addedIn}</span>
<span class="badge bg-primary-lt">Added in {addedIn}</span>
</div>
)
}
</div>
<p class="docs-page-lead text-secondary fs-3 lh-3">{summary}</p>
<p class="docs-page-lead text-secondary fs-3 lh-lg">{summary}</p>
{/* Page must supply headings with ids (MDX does this by default). */}
<slot />
+2 -2
View File
@@ -9,8 +9,8 @@
"scripts": {
"dev": "pnpm run css && concurrently -n astro,css -c blue,magenta \"astro dev\" \"pnpm run watch-css\"",
"build": "pnpm run css && astro build",
"css": "sass scss/:tmp-assets/css/ --no-source-map --load-path=./node_modules",
"watch-css": "sass --watch scss/:tmp-assets/css/ --no-source-map --load-path=./node_modules",
"css": "tsx ../.build/build-css.ts scss tmp-assets/css --minify --no-prefix",
"watch-css": "nodemon --watch scss/ --ext scss --exec \"tsx ../.build/build-css.ts scss tmp-assets/css --no-prefix\"",
"preview": "astro preview",
"clean": "shx rm -rf dist public .astro tmp-assets",
"type-check": "astro check"
+5 -3
View File
@@ -1,6 +1,8 @@
// Docs-only stylesheet, compiled by the `css` script into tmp-assets/css/
// (copy-assets serves it as /css/docs.css). Inlines the DocSearch base styles
// and themes them with Tabler variables.
// Docs-only stylesheet, built by the `css` script through the shared css
// pipeline (.build/build-css.ts, with --no-prefix) into tmp-assets/css/;
// copy-assets serves it as /css/docs.css. Tabler variables are written with
// their public `--tblr-` prefix here: the inlined DocSearch stylesheet below
// has variables its JS sets at runtime, so the build must not rename any.
@use '@docsearch/css/dist/style.css';
// DocSearch ships its own palette — an Algolia blue plus lavender greys — and
+11 -8
View File
@@ -8,6 +8,7 @@
"clean": "turbo clean",
"bundlewatch": "turbo bundlewatch",
"type-check": "turbo type-check",
"type-check:build": "tsc -p .build/tsconfig.json",
"test": "turbo test",
"test:watch": "turbo test:watch",
"test:coverage": "turbo test:coverage",
@@ -21,17 +22,18 @@
"generate-tokens:check": "tsx .build/generate-tokens.ts check",
"check-docs-links": "tsx .build/check-docs-links.ts",
"check-css-vars": "tsx .build/check-css-vars.ts",
"lint": "pnpm run lint-md && pnpm run check-docs-links && pnpm run lint-prettier && pnpm run lint-scss-vars && pnpm run check-css-vars && pnpm run generate-tokens:check",
"lint:fix": "(pnpm run lint-md-fix || true) && (pnpm run lint:scss:fix || true) && (pnpm run format-prettier || true)",
"lint:scss": "pnpm --filter @tabler/core lint:scss",
"lint:scss:fix": "pnpm --filter @tabler/core lint:scss:fix",
"check-html": "tsx .build/check-html.ts",
"check-markup-classes": "tsx .build/check-markup-classes.ts",
"lint": "pnpm run lint-md && pnpm run check-docs-links && pnpm run lint-prettier && pnpm run lint-scss && pnpm run check-css-vars && pnpm run generate-tokens:check",
"lint:fix": "pnpm run lint-md-fix && pnpm run lint-scss-fix && pnpm run format-prettier",
"lint-scss": "pnpm --filter @tabler/core lint",
"lint-scss-fix": "pnpm --filter @tabler/core lint:scss:fix",
"lint-md": "markdownlint --config .markdownlint-docs.json \"docs/content/**/*.mdx\"",
"lint-md-fix": "markdownlint --fix --config .markdownlint-docs.json \"docs/content/**/*.mdx\"",
"lint-prettier": "prettier --check \"core/scss/**/*.scss\" \"core/js/**/*.{js,ts}\" \"core/.build/**/*.{ts,mts,mjs}\" \".build/**/*.{ts,mts}\" \"screenshots/.build/**/*.ts\" \"preview/scss/**/*.scss\" \"docs/scss/**/*.scss\" \"preview/js/**/*.{js,ts}\" \"{preview,docs,screenshots,shared}/**/*.astro\" \"{preview,docs,screenshots}/**/*.{mjs,mts,ts}\" \"shared/**/*.{js,mjs,mts,ts}\" --cache",
"lint-scss-vars": "pnpm --filter @tabler/core css-lint",
"format": "pnpm run format-prettier && pnpm run reformat-md",
"format-prettier": "prettier --write \"core/scss/**/*.scss\" \"core/js/**/*.{js,ts}\" \"core/.build/**/*.{ts,mts,mjs}\" \".build/**/*.{ts,mts}\" \"screenshots/.build/**/*.ts\" \"preview/scss/**/*.scss\" \"docs/scss/**/*.scss\" \"preview/js/**/*.{js,ts}\" \"{preview,docs,screenshots,shared}/**/*.astro\" \"{preview,docs,screenshots}/**/*.{mjs,mts,ts}\" \"shared/**/*.{js,mjs,mts,ts}\" --cache",
"check": "pnpm run lint && pnpm run type-check",
"check": "pnpm run lint && pnpm run type-check && pnpm run type-check:build",
"zip-package": "tsx .build/zip-package.ts",
"start": "pnpm dev"
},
@@ -44,30 +46,31 @@
"@changesets/cli": "^3.0.1",
"@mdx-js/mdx": "^3.1.1",
"@stylistic/stylelint-config": "^2.0.0",
"@types/node": "^26.4.0",
"adm-zip": "^0.6.0",
"autoprefixer": "^10.5.4",
"bundlewatch": "^0.4.2",
"clean-css": "^5.3.3",
"concurrently": "^10.0.5",
"cross-env": "^10.1.0",
"fs-extra": "^11.4.0",
"github-slugger": "^2.0.0",
"glob": "^13.0.6",
"js-beautify": "^2.0.3",
"markdownlint-cli": "^0.49.1",
"nodemon": "^3.1.14",
"playwright": "1.62.1",
"postcss": "^8.5.26",
"postcss-prefix-custom-properties": "^0.1.0",
"prettier": "^3.9.6",
"prettier-plugin-astro": "^0.14.1",
"rtlcss": "^4.3.0",
"sass": "1.103.1",
"shelljs": "^0.10.0",
"stylelint": "^16.26.1",
"stylelint-config-twbs-bootstrap": "^16.1.0",
"terser": "^5.51.2",
"tsx": "^4.23.13",
"turbo": "2.10.12",
"typescript": "^7.0.2",
"vite": "^8.2.2"
}
}
+9 -40
View File
@@ -24,6 +24,9 @@ importers:
'@stylistic/stylelint-config':
specifier: ^2.0.0
version: 2.0.0(stylelint@16.26.1(supports-color@5.5.0)(typescript@7.0.2))
'@types/node':
specifier: ^26.4.0
version: 26.4.0
adm-zip:
specifier: ^0.6.0
version: 0.6.0
@@ -42,9 +45,6 @@ importers:
cross-env:
specifier: ^10.1.0
version: 10.1.0
fs-extra:
specifier: ^11.4.0
version: 11.4.0
github-slugger:
specifier: ^2.0.0
version: 2.0.0
@@ -60,6 +60,9 @@ importers:
nodemon:
specifier: ^3.1.14
version: 3.1.14
playwright:
specifier: 1.62.1
version: 1.62.1
postcss:
specifier: ^8.5.26
version: 8.5.26
@@ -78,9 +81,6 @@ importers:
sass:
specifier: 1.103.1
version: 1.103.1
shelljs:
specifier: ^0.10.0
version: 0.10.0
stylelint:
specifier: ^16.26.1
version: 16.26.1(supports-color@5.5.0)(typescript@7.0.2)
@@ -96,6 +96,9 @@ importers:
turbo:
specifier: 2.10.12
version: 2.10.12
typescript:
specifier: ^7.0.2
version: 7.0.2
vite:
specifier: ^8.2.2
version: 8.2.2(@types/node@26.4.0)(esbuild@0.28.1)(sass@1.103.1)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)
@@ -3049,10 +3052,6 @@ packages:
fraction.js@5.3.4:
resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
fs-extra@11.4.0:
resolution: {integrity: sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==}
engines: {node: '>=14.14'}
fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
@@ -3445,9 +3444,6 @@ packages:
jsonc-parser@3.3.1:
resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==}
jsonfile@6.2.1:
resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==}
jsonpack@1.1.5:
resolution: {integrity: sha512-d2vwomK605ks7Q+uCpbwGyoIF5j+UZuJjlYcugISBt3CxM+eBo/W6y63yVPIyIvbYON+pvJYsYZjCYbzqJj/xQ==}
@@ -4559,10 +4555,6 @@ packages:
resolution: {integrity: sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==}
engines: {node: '>= 0.4'}
shelljs@0.10.0:
resolution: {integrity: sha512-Jex+xw5Mg2qMZL3qnzXIfaxEtBaC4n7xifqaqtrZDdlheR70OGkydrPJWT0V1cA1k3nanC86x9FwAmQl6w3Klw==}
engines: {node: '>=18'}
shelljs@0.9.2:
resolution: {integrity: sha512-S3I64fEiKgTZzKCC46zT/Ib9meqofLrQVbpSswtjFfAVDW+AZ54WTnAM/3/yENoxz/V1Cy6u3kiiEbQ4DNphvw==}
engines: {node: '>=18'}
@@ -4979,10 +4971,6 @@ packages:
unist-util-visit@5.1.0:
resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==}
universalify@2.0.1:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
engines: {node: '>= 10.0.0'}
unstorage@1.17.5:
resolution: {integrity: sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==}
peerDependencies:
@@ -8121,12 +8109,6 @@ snapshots:
fraction.js@5.3.4: {}
fs-extra@11.4.0:
dependencies:
graceful-fs: 4.2.11
jsonfile: 6.2.1
universalify: 2.0.1
fs.realpath@1.0.0: {}
fsevents@2.3.2:
@@ -8610,12 +8592,6 @@ snapshots:
jsonc-parser@3.3.1: {}
jsonfile@6.2.1:
dependencies:
universalify: 2.0.1
optionalDependencies:
graceful-fs: 4.2.11
jsonpack@1.1.5: {}
jsonpointer@5.0.1: {}
@@ -10287,11 +10263,6 @@ snapshots:
shell-quote@1.9.0: {}
shelljs@0.10.0:
dependencies:
execa: 5.1.1
fast-glob: 3.3.3
shelljs@0.9.2:
dependencies:
execa: 1.0.0
@@ -10782,8 +10753,6 @@ snapshots:
unist-util-is: 6.0.1
unist-util-visit-parents: 6.0.2
universalify@2.0.1: {}
unstorage@1.17.5(@vercel/functions@3.9.5(ws@8.21.0)):
dependencies:
anymatch: 3.1.3
+2 -2
View File
@@ -1,8 +1,8 @@
#!/usr/bin/env node
// Regenerate shared/data/icons.json and icons-info.json from the installed
// @tabler/icons package. Run `pnpm run import-icons` to bump the package to
// the latest version and refresh the data files.
// @tabler/icons package. `pnpm run import-icons` refreshes the data files from
// the installed version; `pnpm run update-icons` bumps the package first.
import { readFileSync, writeFileSync } from 'node:fs'
import { join, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
+3 -2
View File
@@ -9,7 +9,7 @@
"scripts": {
"assets": "shx rm -rf tmp-assets && pnpm run css && pnpm run js",
"css": "tsx ../.build/build-css.ts scss tmp-assets/css --minify",
"js": "vite build --config .build/vite.config.mts && terser tmp-assets/js/demo.js --module --compress --mangle --comments '/@license|@preserve|^!/' --source-map \"content=tmp-assets/js/demo.js.map,filename=tmp-assets/js/demo.min.js.map,url=demo.min.js.map\" -o tmp-assets/js/demo.min.js",
"js": "vite build --config .build/vite.config.mts && terser tmp-assets/js/demo.js --module --compress --mangle --source-map \"content=tmp-assets/js/demo.js.map,filename=tmp-assets/js/demo.min.js.map,url=demo.min.js.map\" -o tmp-assets/js/demo.min.js",
"watch-css": "nodemon --watch scss/ --ext scss --exec \"tsx ../.build/build-css.ts scss public/preview/css\"",
"watch-js": "cross-env PREVIEW_JS_OUT_DIR=public/preview/js vite build --watch --config .build/vite.config.mts",
"watch": "concurrently \"pnpm run watch-css\" \"pnpm run watch-js\"",
@@ -18,7 +18,8 @@
"build": "pnpm run assets && astro build",
"preview": "astro preview",
"clean": "shx rm -rf dist public/dist public/preview public/css .astro tmp-assets",
"import-icons": "pnpm up @tabler/icons@latest && node .build/import-icons.mjs",
"import-icons": "node .build/import-icons.mjs",
"update-icons": "pnpm up @tabler/icons@latest && pnpm run import-icons",
"import-illustrations": "node .build/import-illustrations.mjs",
"type-check": "astro check"
},
+1 -1
View File
@@ -10,7 +10,7 @@ import CardTitle from '@ui/CardTitle.astro'
<SingleLayout title="2-Step Verification">
<form class="card card-md" action="./" method="get" novalidate>
<div class="card-body">
<CardTitle class="card-title-lg text-center mb-4">Authenticate Your Account</CardTitle>
<CardTitle class="text-center mb-4">Authenticate Your Account</CardTitle>
<p class="my-4 text-center">
Please confirm your account by entering the authorization code sent to <strong>+1 856-672-8552</strong>.
+1 -1
View File
@@ -12,7 +12,7 @@ import site from '@data/site.json'
import { ucFirst } from '@shared/lib/string-format'
import DocsLink from '@ui/DocsLink.astro'
const colors = ['default', ...Object.keys(site.colors), 'dark', 'light']
const colors = [...Object.keys(site.colors), 'dark', 'light']
const sizes = ['sm', 'md', 'lg']
---
+2 -2
View File
@@ -26,12 +26,12 @@ const messages = chats as Message[]
</div>
</div>
<div class="card-body p-0 scrollable flex-fill">
<div class="nav flex-column nav-pills" role="tablist">
<div class="nav flex-column nav-pills">
{
sidebarPeople.map((person, i) => {
const index = i + 1
return (
<a href={`#chat-${index}`} class:list={['nav-link text-start mw-100 p-3', index === 1 && 'active']} id={`chat-${index}-tab`} data-bs-toggle="pill" role="tab" aria-selected={index === 1 ? 'true' : 'false'}>
<a href="#" class:list={['nav-link text-start mw-100 p-3', index === 1 && 'active']} aria-current={index === 1 ? 'page' : undefined}>
<div class="row align-items-center flex-fill">
<div class="col-auto">
<Avatar person={person} />
+3 -3
View File
@@ -26,11 +26,11 @@ import mails from '@data/mails.json'
<div class="mt-3 nav nav-vertical">
<a href="#" class="nav-link text-danger fw-bold">
<Icon name="inbox" class="me-2" />
Inbox<span class="badge badge-danger ms-auto">{mails.length}</span>
Inbox<span class="badge bg-danger ms-auto">{mails.length}</span>
</a>
<a href="#" class="nav-link"><Icon name="star" class="me-2" />Starred</a>
<a href="#" class="nav-link"><Icon name="clock" class="me-2" />Snoozed</a>
<a href="#" class="nav-link"><Icon name="file" class="me-2" />Draft<span class="badge badge-info ms-auto">32</span></a>
<a href="#" class="nav-link"><Icon name="file" class="me-2" />Draft<span class="badge bg-info ms-auto">32</span></a>
<a href="#" class="nav-link"><Icon name="mail-up" class="me-2" />Sent Mail</a>
<a href="#" class="nav-link"><Icon name="trash" class="me-2" />Trash</a>
<a href="#" class="nav-link"><Icon name="tag" class="me-2" />Important</a>
@@ -65,7 +65,7 @@ import mails from '@data/mails.json'
<Subheader as="h6">Storage</Subheader>
<Progress value={46} class="my-2" />
<p class="text-muted font-13 mb-0">7.02 GB (46%) of 15 GB used</p>
<p class="text-muted mb-0">7.02 GB (46%) of 15 GB used</p>
</div>
</div>
</Offcanvas>
+3 -3
View File
@@ -29,9 +29,9 @@ const buildings = realEstate as Building[]
<div class="col-xl-6 col-lg-8 text-center">
<form novalidate="" class="row row-cols-1 row-cols-md-auto g-3 align-items-center">
<div class="col flex-grow-1">
<input id="inputEmail" type="text" placeholder="Search properties near you..." class="form-control form-control-solid" aria-label="Search properties near you" />
<input id="inputEmail" type="text" placeholder="Search properties near you..." class="form-control" aria-label="Search properties near you" />
</div>
<div class="col"><button type="submit" class="btn btn-teal fw-500">Search</button></div>
<div class="col"><button type="submit" class="btn btn-teal fw-medium">Search</button></div>
</form>
</div>
</div>
@@ -47,7 +47,7 @@ const buildings = realEstate as Building[]
buildings.map((building) => (
<div class="col-md-6 col-lg-4">
<Card>
<a href="#">
<a href="#" aria-label={building.name}>
<div class="img-bg ratio ratio-4x3 card-img-top" style={`background-image: url(../static/marketing/${building.image})`} />
</a>
+1 -1
View File
@@ -7,7 +7,7 @@ import DocsLink from '@ui/DocsLink.astro'
<DefaultLayout title="Navigation" pageMenu="base.navigation" description="Navbar variants — condensed, dark, colored, and with different logo and search layouts.">
<DocsLink slot="page-header-actions" path="/ui/layout/navbars" />
<div class="box">
<div>
<div class="mb-3">
<Navbar menuId="navbar-menu-1" sample condensed transparent personId={3} />
</div>
+1 -1
View File
@@ -13,7 +13,7 @@ import PageTitle from '@ui/PageTitle.astro'
<div class="container">
<div class="row w-100 align-items-center">
<div class="col me-auto">
<Logo class="logo-gray" />
<Logo gray />
</div>
<div class="col-2">
<ProgressSteps count={5} />
+2 -2
View File
@@ -10,8 +10,8 @@ import Icon from '@ui/Icon.astro'
<PayLayout title="Pay" pageLibs={['tabler-payments', 'imask']}>
<div class="bg-dark bg-cover" style="background-image: url(./static/bg-cover.jpg)">
<div class="bg-dark bg-opacity-75 pt-5 bg-blur">
<div class="container container-tight bg-overlay">
<img src="./static/bg-cover.jpg" alt="" class="img-fluid rounded-lg rounded-top" />
<div class="container container-tight">
<img src="./static/bg-cover.jpg" alt="" class="img-fluid rounded-3 rounded-top" />
</div>
</div>
</div>
+10 -10
View File
@@ -42,7 +42,7 @@ import CardBody from '@ui/CardBody.astro'
<div class="dropdown">
<Button element="button" type="button" icon="filter" iconOnly text="Open filters" dropdown />
<div class="dropdown-menu rounded-3 p-6">
<h4 class="fs-lg mb-4">Filter</h4>
<h4 class="fs-3 mb-4">Filter</h4>
<form id="playgroundCategoriesFilterForm" style="width: 350px">
<div class="row align-items-center mb-3">
<div class="col-3">
@@ -91,7 +91,7 @@ import CardBody from '@ui/CardBody.astro'
<div class="dropdown">
<Button element="button" type="button" icon="sort-ascending-letters" iconOnly text="Open sort options" dropdown />
<div class="dropdown-menu rounded-3 p-6">
<h4 class="fs-lg mb-4">Sort</h4>
<h4 class="fs-3 mb-4">Sort</h4>
<form id="playgroundCategoriesSortForm" style="width: 350px">
<div class="row gx-3">
<div class="col">
@@ -138,7 +138,7 @@ import CardBody from '@ui/CardBody.astro'
</div>
<h3 class="fs-5 mb-1 text-truncate"><a class="text-body" href="./empty.html">Announcements</a></h3>
<p class="text-secondary text-truncate">For company updates, system changes, or important notices that everyone should see.</p>
<p class="fs-sm text-secondary mb-0">Last post 2 days ago</p>
<p class="small text-secondary mb-0">Last post 2 days ago</p>
<hr />
<div class="d-flex align-items-center justify-content-between">
<AvatarList stacked={true}>
@@ -167,7 +167,7 @@ import CardBody from '@ui/CardBody.astro'
</div>
<h3 class="fs-5 mb-1 text-truncate"><a class="text-body" href="./empty.html">Marketing campaigns</a></h3>
<p class="text-secondary text-truncate">Posts related to new campaigns, marketing strategies, and promotions.</p>
<p class="fs-sm text-secondary mb-0">Last post 1 day ago</p>
<p class="small text-secondary mb-0">Last post 1 day ago</p>
<hr />
<div class="d-flex align-items-center justify-content-between">
<PeopleAvatarList stacked={true} size="xs" offset={3} limit={3} />
@@ -186,7 +186,7 @@ import CardBody from '@ui/CardBody.astro'
<small class="text-secondary d-block mb-4">12 followers</small>
<h3 class="fs-6 mb-1 text-truncate"><a class="text-body" href="./empty.html">Reports &amp; analytics</a></h3>
<p class="text-secondary text-truncate">Data-driven insights and performance reports across all departments.</p>
<p class="fs-sm text-secondary mb-0">Last post 5 days ago</p>
<p class="small text-secondary mb-0">Last post 5 days ago</p>
<hr />
<div class="d-flex align-items-center justify-content-between">
<AvatarList stacked={true}>
@@ -208,7 +208,7 @@ import CardBody from '@ui/CardBody.astro'
<small class="text-secondary d-block mb-4">16 followers</small>
<h3 class="fs-6 mb-1 text-truncate"><a class="text-body" href="./empty.html">Events &amp; webinars</a></h3>
<p class="text-secondary text-truncate">Upcoming events, webinar recordings, and community meetups.</p>
<p class="fs-sm text-secondary mb-0">Last post 3 days ago</p>
<p class="small text-secondary mb-0">Last post 3 days ago</p>
<hr />
<div class="d-flex align-items-center justify-content-between">
<AvatarList stacked={true}>
@@ -230,7 +230,7 @@ import CardBody from '@ui/CardBody.astro'
<small class="text-secondary d-block mb-4">9 followers</small>
<h3 class="fs-6 mb-1 text-truncate"><a class="text-body" href="./empty.html">Client success stories</a></h3>
<p class="text-secondary text-truncate">Case studies and testimonials from our most satisfied customers.</p>
<p class="fs-sm text-secondary mb-0">Last post 1 week ago</p>
<p class="small text-secondary mb-0">Last post 1 week ago</p>
<hr />
<div class="d-flex align-items-center justify-content-between">
<AvatarList stacked={true}>
@@ -252,7 +252,7 @@ import CardBody from '@ui/CardBody.astro'
<small class="text-secondary d-block mb-4">10 followers</small>
<h3 class="fs-6 mb-1 text-truncate"><a class="text-body" href="./empty.html">Team highlights</a></h3>
<p class="text-secondary text-truncate">Spotlights, shoutouts, and stories from people across the team.</p>
<p class="fs-sm text-secondary mb-0">Last post 4 days ago</p>
<p class="small text-secondary mb-0">Last post 4 days ago</p>
<hr />
<div class="d-flex align-items-center justify-content-between">
<AvatarList stacked={true}>
@@ -274,7 +274,7 @@ import CardBody from '@ui/CardBody.astro'
<small class="text-secondary d-block mb-4">26 followers</small>
<h3 class="fs-6 mb-1 text-truncate"><a class="text-body" href="./empty.html">Training &amp; resources</a></h3>
<p class="text-secondary text-truncate">Access to learning materials, tutorials, or resources for users and staff.</p>
<p class="fs-sm text-secondary mb-0">Last post 6 days ago</p>
<p class="small text-secondary mb-0">Last post 6 days ago</p>
<hr />
<div class="d-flex align-items-center justify-content-between">
<AvatarList stacked={true}>
@@ -296,7 +296,7 @@ import CardBody from '@ui/CardBody.astro'
<small class="text-secondary d-block mb-4">6 followers</small>
<h3 class="fs-6 mb-1 text-truncate"><a class="text-body" href="./empty.html">Industry news</a></h3>
<p class="text-secondary text-truncate">Trends, news, and commentary from around the industry landscape.</p>
<p class="fs-sm text-secondary mb-0">Last post 2 weeks ago</p>
<p class="small text-secondary mb-0">Last post 2 weeks ago</p>
<hr />
<div class="d-flex align-items-center justify-content-between">
<AvatarList stacked={true}>
+1 -1
View File
@@ -9,7 +9,7 @@ const siteEmail = 'support@tabler.io'
<div class="my-5">
<h2 class="h1">Check your inbox</h2>
<p class="fs-h3 text-secondary">
<p class="fs-3 text-secondary">
We've sent you a magic link to <strong>{siteEmail}</strong>.<br />
Please click the link to confirm your address.
</p>
+1 -1
View File
@@ -4,5 +4,5 @@
// Registration is synchronous (a promise) — see page-scripts.ts.
import { addPageScript } from '@shared/lib/page-scripts'
addPageScript(Astro.slots.render('default'))
addPageScript(Astro.request, Astro.slots.render('default'))
---
+2 -2
View File
@@ -1,10 +1,10 @@
---
// Separate component so drainPageScripts() executes only at this point of the
// Separate component so drainPageScripts(Astro.request) executes only at this point of the
// render stream (after the page content render has STARTED — registrations are
// synchronous, script content may be a promise).
import { drainPageScripts } from '@shared/lib/page-scripts'
const scripts = await Promise.all(drainPageScripts())
const scripts = await Promise.all(drainPageScripts(Astro.request))
---
{scripts.length > 0 && <Fragment set:html={scripts.join('\n')} />}
+1 -1
View File
@@ -3,7 +3,7 @@ import ActivityPart from '@shared/components/demo/ActivityPart.astro'
---
<div class="card" style="height: 28rem">
<div class="card-body card-body-scrollable card-body-scrollable-shadow">
<div class="card-body card-body-scrollable">
<ActivityPart />
</div>
</div>
@@ -8,7 +8,7 @@ import CardTitle from '@ui/CardTitle.astro'
<form class="card card-md" action="#" method="get" autocomplete="off" novalidate>
<div class="card-body">
<CardTitle class="card-title-lg text-center mb-4">Authenticate Your Account</CardTitle>
<CardTitle class="text-center mb-4">Authenticate Your Account</CardTitle>
<p class="my-4 text-center">
Please confirm your account by entering the authorization code sent to <strong>+1 856-672-8552</strong>.
+1 -1
View File
@@ -4,7 +4,7 @@ import Button from '@ui/Button.astro'
<div class="card card-gradient card-gradient-rainbow">
<div class="card-body text-center p-5">
<div class="display-5 leading-none mb-2">🍾 🎉</div>
<div class="display-5 lh-1 mb-2">🍾 🎉</div>
<h1>Happy Birthday, Andrew!</h1>
<p class="text-secondary">We truly hope your day is brimming with joy, laughter, and countless moments that bring a smile to your face!</p>
<p class="text-secondary">To celebrate your special day, here's a discount code just for you: <strong class="text-body">NWYR20</strong>. Enjoy <strong class="text-body">20% off</strong> your next purchase!</p>
+1 -1
View File
@@ -38,7 +38,7 @@ const countryNames = Object.fromEntries((countries as { code: string; name: stri
<thead>
<tr>
<th class="w-1"><input class="form-check-input m-0 align-middle" type="checkbox" aria-label="Select all invoices" /></th>
<th class="w-1">No. <Icon name="chevron-up" class="icon-sm icon-thick" /></th>
<th class="w-1">No. <Icon name="chevron-up" class="icon-sm" /></th>
<th>Invoice Subject</th>
<th>Client</th>
<th>VAT No.</th>
+1 -1
View File
@@ -6,7 +6,7 @@ import Button from '@ui/Button.astro'
<div class="card card-gradient card-gradient-love">
<div class="ribbon bg-pink">Featured</div>
<div class="card-body">
<div class="h-subheader text-pink">Professional</div>
<div class="subheader text-pink">Professional</div>
<div class="d-flex align-items-baseline mt-5">
<div class="display-5">$199</div>
<div class="fs-1 text-secondary">/ month</div>
+1 -1
View File
@@ -14,7 +14,7 @@ const activity = crmDashboard.recent_activity
<a href="#" class="small fw-medium text-primary">{activity.view_all_text}</a>
</CardActions>
</div>
<div class="card-body card-body-scrollable card-body-scrollable-shadow">
<div class="card-body card-body-scrollable">
<div class="divide-y">
{
activity.items.map((item) => (
Loaded 100 of 126 files, more files were not shown because too many files have changed in this diff. Show more