├── .nvmrc ├── .github ├── FUNDING.yml ├── workflows │ ├── test.yml │ ├── has-changes.yml │ └── test-older-node.yml └── dependabot.yml ├── public ├── favicon.ico └── images │ ├── css.ico │ ├── css.png │ ├── css.2x.png │ ├── cssdb.jpg │ ├── css.square.jpg │ ├── share-default.png │ ├── browsers │ ├── android.png │ ├── mob.svg │ ├── mob@dark.svg │ ├── samsung.svg │ ├── bb.svg │ ├── bb@dark.svg │ ├── and.svg │ ├── webview_android.svg │ ├── opera.svg │ ├── chrome.svg │ ├── oculus.svg │ ├── ie.svg │ ├── baidu.svg │ ├── baidu@dark.svg │ ├── webview_ios.svg │ ├── ios_saf.svg │ ├── edge.svg │ ├── and_uc.svg │ ├── android.svg │ ├── safari.svg │ ├── and_qq.svg │ └── firefox.svg │ ├── css.square-large.jpg │ ├── stages │ ├── stage-X.svg │ ├── stage-0.svg │ ├── stage-1.svg │ ├── stage-2.svg │ ├── stage-3.svg │ └── stage-4.svg │ ├── css.svg │ ├── cssdb.svg │ ├── css-tools.svg │ └── css-tools-dark.svg ├── .gitignore ├── tasks ├── render-site-to-stdout.mjs ├── write-baseline-badges.mjs ├── check-web-features.mjs ├── render-site.mjs ├── check-doc-links.mjs ├── write-stage-badges.mjs ├── populate-db.mjs ├── preview-site.mjs └── test.cjs ├── postcss.config.cjs ├── .editorconfig ├── SEMVER.md ├── src ├── components │ ├── feature-polyfills.mjs │ ├── footer.mjs │ ├── features.mjs │ ├── header.mjs │ ├── feature-support-stats.mjs │ ├── feature-polyfill.mjs │ ├── feature.mjs │ └── stages.mjs ├── util │ └── html.mjs ├── pages │ └── index.mjs └── styles │ └── style.css ├── SECURITY.md ├── utils ├── web-features-data.mjs ├── get.mjs ├── release-date-for-browser-version.mjs ├── mdn-to-browserslist.mjs ├── scan-for-next-browser-version.mjs ├── apply-browser-overrides.mjs ├── baseline-data.mjs ├── feature-example.js ├── supported-browsers-from-mdn.mjs └── baseline-status.mjs ├── LICENSE.md ├── README.md ├── package.json ├── CODE_OF_CONDUCT.md ├── STAGES.md ├── CONTRIBUTING.md └── CHANGELOG.md /.nvmrc: -------------------------------------------------------------------------------- 1 | v20 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: csstools 2 | open_collective: csstools 3 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csstools/cssdb/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/images/css.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csstools/cssdb/HEAD/public/images/css.ico -------------------------------------------------------------------------------- /public/images/css.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csstools/cssdb/HEAD/public/images/css.png -------------------------------------------------------------------------------- /public/images/css.2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csstools/cssdb/HEAD/public/images/css.2x.png -------------------------------------------------------------------------------- /public/images/cssdb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csstools/cssdb/HEAD/public/images/cssdb.jpg -------------------------------------------------------------------------------- /public/images/css.square.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csstools/cssdb/HEAD/public/images/css.square.jpg -------------------------------------------------------------------------------- /public/images/share-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csstools/cssdb/HEAD/public/images/share-default.png -------------------------------------------------------------------------------- /public/images/browsers/android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csstools/cssdb/HEAD/public/images/browsers/android.png -------------------------------------------------------------------------------- /public/images/css.square-large.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csstools/cssdb/HEAD/public/images/css.square-large.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | gh-pages 2 | node_modules 3 | dist 4 | public/images/badges 5 | public/images/badges-baseline 6 | public/badge 7 | -------------------------------------------------------------------------------- /tasks/render-site-to-stdout.mjs: -------------------------------------------------------------------------------- 1 | import { renderIndex } from "../src/pages/index.mjs"; 2 | 3 | process.stdout.write(renderIndex()); 4 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const postcssPresetEnv = require('postcss-preset-env'); 2 | 3 | module.exports = (ctx) => { 4 | return { 5 | map: ctx.options.map, 6 | plugins: [ 7 | postcssPresetEnv({ 8 | stage: 0, 9 | }) 10 | ], 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = tab 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | 13 | [*.{json,md,yml}] 14 | indent_size = 2 15 | indent_style = space 16 | -------------------------------------------------------------------------------- /public/images/stages/stage-X.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/images/browsers/mob.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/browsers/mob@dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/stages/stage-0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Stage 4 | 0 5 | 6 | -------------------------------------------------------------------------------- /public/images/stages/stage-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Stage 4 | 1 5 | 6 | -------------------------------------------------------------------------------- /public/images/stages/stage-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Stage 4 | 2 5 | 6 | -------------------------------------------------------------------------------- /public/images/stages/stage-3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Stage 4 | 3 5 | 6 | -------------------------------------------------------------------------------- /public/images/stages/stage-4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Stage 4 | 4 5 | 6 | -------------------------------------------------------------------------------- /SEMVER.md: -------------------------------------------------------------------------------- 1 | # cssdb Semantic Versioning 2 | 3 | Given a version number **MAJOR**.**MINOR**.**PATCH**, increment the: 4 | 5 | 1. **MAJOR** version for incompatible API changes, or when features move 6 | backward in the [staging process]; 7 | 2. **MINOR** version for backwards-compatible API changes, or new features, or 8 | when features move foreward in the [staging process], 9 | 3. **PATCH** version for backwards-compatible fixes, or supplemental changes 10 | to features. 11 | 12 | [semver]: https://semver.org/ 13 | [staging process]: STAGES.md 14 | -------------------------------------------------------------------------------- /src/components/feature-polyfills.mjs: -------------------------------------------------------------------------------- 1 | import { html } from "../util/html.mjs"; 2 | import { renderFeaturePolyfill } from "./feature-polyfill.mjs"; 3 | 4 | export function renderFeaturePolyfills(polyfills, id, title) { 5 | if (!polyfills || !polyfills.length) { 6 | return ''; 7 | } 8 | 9 | return html` 10 |
11 | Use with a 12 | 17 |
18 | `; 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | push: 4 | 5 | concurrency: 6 | group: test-branch-node-${{ github.ref }} 7 | cancel-in-progress: true 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | node: [20, 'lts/*'] 15 | steps: 16 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 17 | - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f 18 | with: 19 | node-version: ${{ matrix.node }} 20 | 21 | - run: npm ci --ignore-scripts 22 | - run: npm run test 23 | -------------------------------------------------------------------------------- /.github/workflows/has-changes.yml: -------------------------------------------------------------------------------- 1 | name: Has Changes 2 | on: 3 | push: 4 | 5 | jobs: 6 | has-changes: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 10 | with: 11 | ref: ${{ github.head_ref }} 12 | - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f 13 | with: 14 | node-version: 'lts/*' 15 | - run: npm ci --ignore-scripts 16 | - run: npm run preparesite 17 | - name: Check if there are changes 18 | id: changes 19 | run: git diff --quiet --exit-code 20 | -------------------------------------------------------------------------------- /public/images/browsers/samsung.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Only the latest version of plugins and packages will receive security patches. 6 | Please reach out if you need extended support for an older version. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :x: | 13 | | < 4.0 | :x: | 14 | 15 | ## Security contact information 16 | 17 | To report a security vulnerability, please use the 18 | [Tidelift security contact](https://tidelift.com/security). 19 | Tidelift will coordinate the fix and disclosure. 20 | -------------------------------------------------------------------------------- /tasks/write-baseline-badges.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | import cssdb from 'cssdb'; 3 | import { baselineIcon, baselineStatus } from '../utils/baseline-status.mjs'; 4 | const badgesDirURL = new URL('../public/images/badges-baseline/', import.meta.url); 5 | 6 | await fs.rm(badgesDirURL, { force: true, recursive: true }); 7 | await fs.mkdir(badgesDirURL); 8 | 9 | await Promise.all(cssdb.map((feature) => { 10 | const shield = baselineIcon(...baselineStatus(feature)); 11 | 12 | const badgeURL = new URL(`../public/images/badges-baseline/${feature.id}.svg`, import.meta.url); 13 | 14 | return Promise.all([ 15 | fs.writeFile(badgeURL, shield), 16 | ]); 17 | })); 18 | -------------------------------------------------------------------------------- /utils/web-features-data.mjs: -------------------------------------------------------------------------------- 1 | import { features } from 'web-features'; 2 | 3 | export function applyWebFeaturesData(feature) { 4 | const webFeatureName = feature['web-feature']; 5 | if (!webFeatureName) { 6 | return; 7 | } 8 | 9 | const webFeature = features[webFeatureName]; 10 | if (!webFeature) { 11 | throw new Error(`Unknown web feature: ${webFeatureName}`); 12 | } 13 | 14 | if (!feature.mdn_path && webFeature.compat_features) { 15 | feature.mdn_path = webFeature.compat_features; 16 | } 17 | 18 | if (!feature.title && webFeature.name) { 19 | feature.title = webFeature.name; 20 | } 21 | 22 | if (!feature.specification && webFeature.spec) { 23 | feature.specification = webFeature.spec; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /utils/get.mjs: -------------------------------------------------------------------------------- 1 | export function get(object, keys) { 2 | let _keys; 3 | if (typeof keys === 'string') { 4 | _keys = keys.split('.') 5 | } else { 6 | _keys = keys.slice(); 7 | } 8 | 9 | const key = _keys.shift(); 10 | if (!key) { 11 | return null; 12 | } 13 | 14 | if (key === 'prototype' || key === '__proto__') { 15 | return null; 16 | } 17 | 18 | let hasOwnProperty = false; 19 | try { 20 | hasOwnProperty = Object.prototype.hasOwnProperty.call(object, key) 21 | } catch { 22 | return null; 23 | } 24 | 25 | if (hasOwnProperty) { 26 | const value = object[key]; 27 | 28 | if (_keys.length === 0) { 29 | return value; 30 | } else { 31 | return get(value, _keys); 32 | } 33 | } 34 | 35 | return null; 36 | } 37 | -------------------------------------------------------------------------------- /utils/release-date-for-browser-version.mjs: -------------------------------------------------------------------------------- 1 | import bcd from '@mdn/browser-compat-data' with { type: 'json' }; 2 | 3 | export function releaseDateForBrowserVersion(browser, version) { 4 | const dateStr = bcd.browsers?.[browser]?.releases?.[version]?.release_date; 5 | if (!dateStr) { 6 | return; 7 | } 8 | 9 | // If the first version of a browser implements a feature, then the date isn't a significant marker. 10 | // A new browser can be created with support for all features in 2030, that doesn't indicate that those features are new in 2030. 11 | const firstVersion = Object.keys(bcd.browsers[browser].releases)[0]; 12 | if (version === firstVersion) { 13 | return -1; 14 | } 15 | 16 | return (new Date(dateStr)).getTime() / 1000; 17 | } 18 | -------------------------------------------------------------------------------- /utils/mdn-to-browserslist.mjs: -------------------------------------------------------------------------------- 1 | export const MDNToBrowserlistMap = { 2 | chrome_android: 'and_chr', 3 | safari_ios: 'ios_saf', 4 | firefox_android: 'and_ff', 5 | opera_android: 'op_mob', 6 | samsunginternet_android: 'samsung', 7 | webview_android: 'android', 8 | }; 9 | 10 | export function MDNToBrowserlist(browser) { 11 | return MDNToBrowserlistMap[browser] || browser; 12 | } 13 | 14 | export const BrowserslistToMDNMap = { 15 | and_chr: 'chrome_android', 16 | ios_saf: 'safari_ios', 17 | and_ff: 'firefox_android', 18 | op_mob: 'opera_android', 19 | samsung: 'samsunginternet_android', 20 | android: 'webview_android', 21 | }; 22 | 23 | export function BrowserslistToMDN(browser) { 24 | return BrowserslistToMDNMap[browser] || browser; 25 | } 26 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | time: "01:00" 8 | timezone: "Europe/Brussels" 9 | cooldown: 10 | default-days: 7 11 | open-pull-requests-limit: 10 12 | versioning-strategy: increase 13 | rebase-strategy: auto 14 | groups: 15 | all-dependencies: 16 | patterns: 17 | - "*" 18 | - package-ecosystem: "github-actions" 19 | directory: "/" 20 | schedule: 21 | interval: weekly 22 | time: "01:00" 23 | timezone: Europe/Brussels 24 | cooldown: 25 | default-days: 7 26 | open-pull-requests-limit: 10 27 | rebase-strategy: auto 28 | groups: 29 | all-dependencies: 30 | patterns: 31 | - "*" 32 | -------------------------------------------------------------------------------- /.github/workflows/test-older-node.yml: -------------------------------------------------------------------------------- 1 | name: test-older-node 2 | on: 3 | push: 4 | 5 | concurrency: 6 | group: test-older-node-branch-node-${{ github.ref }} 7 | cancel-in-progress: true 8 | 9 | # TODO : delete this workflow on next major if we drop node 12, 14, 16 and 18 10 | 11 | jobs: 12 | test-older-node: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | node: [14, 16, 18] 17 | steps: 18 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 19 | - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f 20 | with: 21 | node-version: ${{ matrix.node }} 22 | 23 | - run: | 24 | NPM_CONFIG_ENGINE_STRICT=false npm install --ignore-scripts 25 | - run: npm run test:json 26 | -------------------------------------------------------------------------------- /src/components/footer.mjs: -------------------------------------------------------------------------------- 1 | import { html } from "../util/html.mjs"; 2 | 3 | export function renderFooter() { 4 | return html` 5 | 15 | `; 16 | } 17 | -------------------------------------------------------------------------------- /public/images/browsers/bb.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/browsers/bb@dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/browsers/and.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/browsers/webview_android.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/components/features.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | import cssdb from 'cssdb'; 3 | import { html } from '../util/html.mjs'; 4 | import { renderFeature } from './feature.mjs'; 5 | 6 | const cssdbSettingsURL = new URL('../../cssdb.settings.json', import.meta.url); 7 | 8 | const cssdbSettings = await fs.readFile(cssdbSettingsURL, 'utf8').then(JSON.parse); 9 | 10 | export function renderFeatures() { 11 | const features = cssdbSettings.map((feature, index) => ({ ...feature, ...cssdb[index] })) 12 | .sort((a, b) => b.stage - a.stage || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)) 13 | .filter(feature => feature.stage >= 0); 14 | 15 | return html` 16 |
17 | ${features.map((feature) => { 18 | return renderFeature(feature); 19 | }).join('')} 20 |
21 | `; 22 | } 23 | -------------------------------------------------------------------------------- /src/components/header.mjs: -------------------------------------------------------------------------------- 1 | import { html } from "../util/html.mjs"; 2 | 3 | export function renderHeader() { 4 | return html` 5 |
6 | 7 | 11 | CSS Tools 12 | 13 |
14 |

15 | What’s next for CSS? 16 |

17 |

18 | cssdb is a list of CSS features and their positions in the process of becoming implemented web standards. 19 |

20 |
21 | What are the stages? 22 |
23 | `; 24 | } 25 | -------------------------------------------------------------------------------- /src/util/html.mjs: -------------------------------------------------------------------------------- 1 | // Enable "html``" tagged template literals with html syntax highlighting. 2 | export function html(strings, ...values) { 3 | return String.raw({ raw: strings }, ...values); 4 | } 5 | 6 | const htmlEntities = { 7 | '&': '&', 8 | '<': '<', 9 | '>': '>', 10 | "'": ''', 11 | '"': '"' 12 | }; 13 | 14 | export function escapeHTML(string) { 15 | return string.replace(/[&<>'"]/g, (tag) => { 16 | return htmlEntities[tag]; 17 | }); 18 | } 19 | 20 | export function backTicksToCodeTags(string) { 21 | let result = ''; 22 | let inBackticks = false; 23 | 24 | for (let i = 0; i < string.length; i++) { 25 | if (string[i] === '`') { 26 | if (inBackticks) { 27 | result += ''; 28 | inBackticks = false; 29 | } else { 30 | result += ''; 31 | inBackticks = true; 32 | } 33 | 34 | continue; 35 | } 36 | 37 | result += string[i]; 38 | } 39 | 40 | return result; 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT No Attribution (MIT-0) 2 | 3 | Copyright © CSS Tools Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the “Software”), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so. 11 | 12 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 18 | SOFTWARE. 19 | -------------------------------------------------------------------------------- /public/images/browsers/opera.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /utils/scan-for-next-browser-version.mjs: -------------------------------------------------------------------------------- 1 | import bcd from '@mdn/browser-compat-data' with { type: 'json' }; 2 | import semver from 'semver'; 3 | 4 | export function scanForNextBrowserVersionWithReleaseDate(browser, version, cb) { 5 | if (!browser || !version) { 6 | return; 7 | } 8 | 9 | { 10 | const result = cb(browser, version); 11 | if (result) { 12 | return result; 13 | } 14 | } 15 | 16 | const versionNames = Object.keys(bcd.browsers?.[browser]?.releases); 17 | const thisVersion = semver.coerce(version); 18 | if (!thisVersion) { 19 | return; 20 | } 21 | 22 | const thisVersionOrLater = versionNames.find(v => { 23 | return semver.gte(semver.coerce(v), thisVersion); 24 | }); 25 | const thisVersionIndex = versionNames.indexOf(thisVersionOrLater); 26 | if (thisVersionIndex === -1) { 27 | return; 28 | } 29 | 30 | for (let i = 1; i < 10; i++) { 31 | const result = cb(browser, version); 32 | if (result) { 33 | return result; 34 | } 35 | 36 | version = versionNames[thisVersionIndex + i]; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /public/images/browsers/chrome.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/images/browsers/oculus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tasks/check-web-features.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | import webFeatures from 'web-features'; 3 | 4 | const cssdbJsonURL = new URL('../cssdb.settings.json', import.meta.url); 5 | 6 | const features = await fs.readFile(cssdbJsonURL, 'utf8').then(JSON.parse); 7 | 8 | let hasBadLinks = false; 9 | let hasMissingDocs = false; 10 | 11 | for (const feature of features) { 12 | if (feature['web-feature']) { 13 | continue; 14 | } 15 | 16 | if (!feature.mdn_path) { 17 | continue; 18 | } 19 | 20 | const mdn_paths = new Set(Array.isArray(feature.mdn_path) ? feature.mdn_path : [feature.mdn_path]); 21 | 22 | for (const webFeatureName in webFeatures) { 23 | const webFeature = webFeatures[webFeatureName]; 24 | if (!webFeature.compat_features) { 25 | continue; 26 | } 27 | 28 | for (const compatFeature of webFeature.compat_features) { 29 | if (mdn_paths.has(compatFeature)) { 30 | console.log(`${feature.id}: found ${compatFeature} in ${webFeatureName}`); 31 | } 32 | } 33 | } 34 | } 35 | 36 | if (hasBadLinks || hasMissingDocs) { 37 | process.exit(1); 38 | } 39 | 40 | process.exit(0); 41 | -------------------------------------------------------------------------------- /utils/apply-browser-overrides.mjs: -------------------------------------------------------------------------------- 1 | export default function applyBrowserOverrides(feature, realValues, overrides) { 2 | for (const key in overrides) { 3 | const from = overrides[key].from; 4 | const to = overrides[key].to; 5 | 6 | if (typeof realValues[key] === 'undefined' && from === null) { 7 | // We can not define "undefined" in JSON, because that is not how JSON works. 8 | realValues[key] = to; 9 | continue; 10 | } 11 | 12 | if (realValues[key] !== from) { 13 | // We only want to keep overrides in "cssdb.settings.json" for up until upstream issues or questions in MDN browser compat data have been resolved. 14 | // If the MDN data changes we want to throw an error so that we know to update "cssdb.settings.json". 15 | throw new Error(errorMessage(feature, key, from, realValues[key])); 16 | } 17 | 18 | realValues[key] = to; 19 | 20 | if (to === null) { 21 | delete realValues[key]; 22 | } 23 | } 24 | 25 | return realValues; 26 | } 27 | 28 | function errorMessage(feature, browser, from, realValue) { 29 | return `The overrides for ${browser} in ${feature} might no longer be needed. MDN used to have ${from} and now has ${realValue}.`; 30 | } 31 | -------------------------------------------------------------------------------- /tasks/render-site.mjs: -------------------------------------------------------------------------------- 1 | import fsSync, { promises as fs } from 'fs'; 2 | import { renderIndex } from "../src/pages/index.mjs"; 3 | import path from 'path'; 4 | import { globSync } from 'glob'; 5 | 6 | // Cleanup 7 | { 8 | await fs.rm(new URL('../dist/badge', import.meta.url), { 9 | recursive: true, 10 | force: true, 11 | }); 12 | 13 | await fs.rm(new URL('../dist/images', import.meta.url), { 14 | recursive: true, 15 | force: true, 16 | }); 17 | 18 | await fs.rm(new URL('../dist/favicon.ico', import.meta.url), { 19 | recursive: true, 20 | force: true, 21 | }); 22 | 23 | await fs.mkdir(new URL('../dist/', import.meta.url), { 24 | recursive: true, 25 | }); 26 | } 27 | 28 | // HTML 29 | { 30 | await fs.writeFile(new URL('../dist/index.html', import.meta.url), renderIndex()) 31 | } 32 | 33 | // Assets 34 | { 35 | globSync(path.join('./public', '**/*'), { 36 | nodir: true, 37 | }).forEach((file) => { 38 | const outPath = path.join('./dist', file.replace(path.normalize('./public/'), '')); 39 | fsSync.mkdirSync(path.parse(outPath).dir, { 40 | recursive: true, 41 | }); 42 | 43 | fsSync.copyFileSync( 44 | path.normalize(file), 45 | outPath 46 | ); 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /public/images/browsers/ie.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/browsers/baidu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/browsers/baidu@dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tasks/check-doc-links.mjs: -------------------------------------------------------------------------------- 1 | import fs from "node:fs/promises" 2 | 3 | const cssdb = JSON.parse(await fs.readFile('cssdb.settings.json'), 'utf-8'); 4 | 5 | let hasBadLinks = false; 6 | let hasMissingDocs = false; 7 | 8 | for (const feature of cssdb) { 9 | if (!feature.docs && feature.vendors_implementations > 0) { 10 | console.log(`No docs for "${feature.id}" with ${feature.vendors_implementations} vendors implementing`); 11 | hasMissingDocs = true; 12 | } 13 | 14 | if (!feature.docs) { 15 | continue; 16 | } 17 | 18 | for (const provider in feature.docs) { 19 | const url = feature.docs[provider]; 20 | 21 | const resp = await fetch(url, { 22 | redirect: 'manual' 23 | }); 24 | 25 | if (!resp.ok) { 26 | if (resp.status === 301) { 27 | console.log(`Bad response for "${feature.id}" when checking "${provider}" docs:\n "${new URL(resp.headers.get('location'), url).href}"`); 28 | feature.docs[provider] = new URL(resp.headers.get('location'), url).href; 29 | hasBadLinks = true; 30 | } else { 31 | console.log(`Bad response for "${feature.id}" when checking "${provider}" docs:\n ${resp.status} ${resp.statusText}`); 32 | hasBadLinks = true; 33 | } 34 | } 35 | } 36 | } 37 | 38 | // await fs.writeFile('cssdb.settings.json', JSON.stringify(cssdb, null, ' ')) 39 | 40 | if (hasBadLinks || hasMissingDocs) { 41 | process.exit(1); 42 | } 43 | 44 | process.exit(0); 45 | -------------------------------------------------------------------------------- /public/images/browsers/webview_ios.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/browsers/ios_saf.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cssdb [][cssdb] 2 | 3 | [![NPM Version][npm-img]][npm-url] 4 | [![Build Status][cli-img]][cli-url] 5 | 6 | [cssdb] is a list of CSS features and their positions in 7 | the process of becoming implemented web standards. 8 | 9 | --- 10 | 11 | Did you come here to update the status of a CSS feature or add a new one? 12 | Quick, read [CONTRIBUTING.md](CONTRIBUTING.md). 13 | 14 | Did you come here to learn about the stages? Quick, read [STAGES.md](STAGES.md). 15 | 16 | --- 17 | 18 | [cssdb] ranks CSS features by stages that reflect the real-life stability of 19 | new CSS features. 20 | 21 | You can read an [inside view of the CSSWG] to learn about the official 22 | (and unofficial) development stages of CSS specifications. In reality, 23 | specifications and browser implementations happen out of sync. For example, 24 | there are stable CSS features missing in all browsers, while other CSS features 25 | developed outside the [CSSWG] have appeared in browsers behind flags. This is 26 | too ambiguous for the web development community, and a more accountable process 27 | is desired. 28 | 29 | [cli-img]: https://github.com/csstools/cssdb/actions/workflows/test.yml/badge.svg 30 | [cli-url]: https://github.com/csstools/cssdb/actions/workflows/test.yml 31 | [cssdb]: https://github.com/csstools/cssdb 32 | [CSSWG]: https://wiki.csswg.org/spec 33 | [inside view of the CSSWG]: https://fantasai.inkedblade.net/weblog/2011/inside-csswg/process 34 | [npm-img]: https://img.shields.io/npm/v/cssdb.svg 35 | [npm-url]: https://www.npmjs.com/package/cssdb 36 | -------------------------------------------------------------------------------- /src/components/feature-support-stats.mjs: -------------------------------------------------------------------------------- 1 | import { escapeHTML, html } from "../util/html.mjs"; 2 | 3 | const browsers = { 4 | 'ios_saf': 'iOS Safari', 5 | 'op_mob': 'Opera Mobile', 6 | 'and_uc': 'UC Mobile for Android', 7 | 'and_chr': 'Chrome for Android', 8 | 'and_ff': 'Firefox for Android', 9 | 'and_qq': 'QQ Browser', 10 | 'webview_ios': 'WKWebview on iOS', 11 | 'webview_android': 'Android WebView' 12 | }; 13 | 14 | // Similar order as MDN 15 | const browser_order = [ 16 | // Desktop 17 | "chrome", 18 | "edge", 19 | "firefox", 20 | "ie", 21 | "opera", 22 | "safari", 23 | // Mobile 24 | "and_chr", 25 | "and_ff", 26 | "op_mob", 27 | "ios_saf", 28 | "samsung", 29 | "android", 30 | // Headset 31 | "oculus", 32 | // Webviews 33 | 'webview_android', 34 | 'webview_ios', 35 | ]; 36 | 37 | export function renderFeatureSupportStats(title, feature) { 38 | let link = ''; 39 | if (feature.docs && feature.docs.mdn) { 40 | link = `${feature.docs.mdn}#browser_compatibility`; 41 | } 42 | 43 | const feature_browsers = Object.keys(feature.browser_support); 44 | feature_browsers.sort((a, b) => { 45 | const a_index = browser_order.indexOf(a); 46 | const b_index = browser_order.indexOf(b); 47 | if (a_index === -1) { 48 | return 1; 49 | } 50 | if (b_index === -1) { 51 | return -1; 52 | } 53 | return a_index - b_index; 54 | }); 55 | 56 | return html` 57 | 0) ? `href="${link}"` : ''} 60 | target="_blank" 61 | rel="noreferrer" 62 | aria-label="Browser support. Opens in a new tab." 63 | > 64 | ${(feature_browsers.map((id) => { 65 | return html` 66 | Supported on ${browsers[id] || id} since version 67 | ${escapeHTML(feature.browser_support[id])} 68 | `; 69 | }).join(''))} 70 | `; 71 | } 72 | -------------------------------------------------------------------------------- /utils/baseline-data.mjs: -------------------------------------------------------------------------------- 1 | import { BrowserslistToMDN } from "./mdn-to-browserslist.mjs"; 2 | import { releaseDateForBrowserVersion } from "./release-date-for-browser-version.mjs"; 3 | import { scanForNextBrowserVersionWithReleaseDate } from "./scan-for-next-browser-version.mjs"; 4 | 5 | const baselineEngines = { 6 | blink: [ 7 | 'and_chr', 8 | 'chrome', 9 | 'edge', 10 | ], 11 | gecko: [ 12 | 'firefox', 13 | 'and_ff', 14 | ], 15 | webkit: [ 16 | 'safari', 17 | 'ios_saf' 18 | ] 19 | }; 20 | 21 | const engines = { 22 | blink: [ 23 | 'and_chr', 24 | 'chrome', 25 | 'edge', 26 | ], 27 | gecko: [ 28 | 'firefox', 29 | 'and_ff', 30 | ], 31 | trident: [ 32 | 'ie', 33 | ], 34 | webkit: [ 35 | 'safari', 36 | 'ios_saf' 37 | ] 38 | }; 39 | 40 | export function baselineData(feature) { 41 | let supportedEngines = 0; 42 | let latestReleaseDate; 43 | 44 | for (const engine in baselineEngines) { 45 | let supportedBrowsers = 0; 46 | const browsersInEngine = engines[engine]; 47 | for (const browser of browsersInEngine) { 48 | const releaseDate = scanForNextBrowserVersionWithReleaseDate(BrowserslistToMDN(browser), feature.browser_support[browser], releaseDateForBrowserVersion); 49 | if (!releaseDate) { 50 | continue; 51 | } 52 | 53 | supportedBrowsers++; 54 | } 55 | 56 | if (supportedBrowsers === browsersInEngine.length) { 57 | supportedEngines++; 58 | } 59 | } 60 | 61 | for (const engine in engines) { 62 | for (const browser of engines[engine]) { 63 | const releaseDate = scanForNextBrowserVersionWithReleaseDate(BrowserslistToMDN(browser), feature.browser_support[browser], releaseDateForBrowserVersion); 64 | if (!releaseDate) { 65 | continue; 66 | } 67 | 68 | if (!latestReleaseDate || releaseDate > latestReleaseDate) { 69 | latestReleaseDate = releaseDate; 70 | } 71 | } 72 | } 73 | 74 | if (supportedEngines < 3) { 75 | latestReleaseDate = undefined; 76 | } 77 | 78 | return [ 79 | supportedEngines, 80 | latestReleaseDate 81 | ]; 82 | } 83 | -------------------------------------------------------------------------------- /tasks/write-stage-badges.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | import cssdb from 'cssdb'; 3 | const badgesDirURL = new URL('../public/images/badges/', import.meta.url); 4 | const badgesDirURLOld = new URL('../public/badge/', import.meta.url); 5 | 6 | const colors = [ 7 | '414141', 8 | 'ed782a', 9 | '899c1f', 10 | '3e7817', 11 | '005a9c' 12 | ]; 13 | 14 | function renderBadgeSVG(label, status, color) { 15 | // result from : `https://img.shields.io/badge/${encodeURICompoment(shieldSubject)}-${encodeURICompoment(shieldStatus)}-${encodeURICompoment(shieldColor)}.svg?style=flat-square`; 16 | return `${label}: ${status}${label}${status}` 17 | } 18 | 19 | await fs.rm(badgesDirURL, { force: true, recursive: true }); 20 | await fs.mkdir(badgesDirURL); 21 | 22 | await fs.rm(badgesDirURLOld, { force: true, recursive: true }); 23 | await fs.mkdir(badgesDirURLOld); 24 | 25 | await Promise.all(cssdb.map((feature) => { 26 | const shieldStatus = feature.stage === -1 ? 'Rejected' : `Stage ${feature.stage}`; 27 | const shieldColor = colors[feature.stage] || 'd02c2c'; 28 | const shield = renderBadgeSVG('cssdb', shieldStatus, shieldColor) 29 | 30 | const badgeURL = new URL(`../public/images/badges/${feature.id}.svg`, import.meta.url); 31 | const badgeURLOld = new URL(`../public/badge/${feature.id}.svg`, import.meta.url); 32 | 33 | return Promise.all([ 34 | fs.writeFile(badgeURL, shield), 35 | fs.writeFile(badgeURLOld, shield) 36 | ]); 37 | })); 38 | -------------------------------------------------------------------------------- /src/pages/index.mjs: -------------------------------------------------------------------------------- 1 | import { renderFeatures } from "../components/features.mjs"; 2 | import { renderFooter } from "../components/footer.mjs"; 3 | import { renderHeader } from "../components/header.mjs"; 4 | import { renderStages } from "../components/stages.mjs"; 5 | import { html } from "../util/html.mjs"; 6 | 7 | export function renderIndex() { 8 | return html` 9 | 10 | 11 | 12 | CSS Database - CSS Tools 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | ${renderHeader()} 37 | ${renderFeatures()} 38 | ${renderStages()} 39 | ${renderFooter()} 40 | 41 | 42 | `; 43 | } 44 | -------------------------------------------------------------------------------- /utils/feature-example.js: -------------------------------------------------------------------------------- 1 | import postcss from 'postcss'; 2 | 3 | function postcssToHTML(root, builder) { 4 | function toString (node) { 5 | if ('atrule' === node.type) { 6 | return atruleToString(node); 7 | } if ('rule' === node.type) { 8 | return ruleToString(node); 9 | } else if ('decl' === node.type) { 10 | return declToString(node); 11 | } else if ('comment' === node.type) { 12 | return commentToString(node); 13 | } else { 14 | return node.nodes ? node.nodes.map(childNodes => toString(childNodes)).join('') : ''; 15 | } 16 | } 17 | 18 | function replaceVars (string) { 19 | return string 20 | .replace(//g, '>') 22 | .replace(/:?--[\w-]+/g, '$&') 23 | } 24 | 25 | function replaceVarsAndFns (string) { 26 | return replaceVars(string) 27 | .replace(/(:?[\w-]+)\(/g, '$1(') 28 | .replace(/"[^"]+"/g, '$&') 29 | } 30 | 31 | function atruleToString (atrule) { 32 | return `${atrule.raws.before||''}@${atrule.name}${atrule.raws.afterName||''}${replaceVarsAndFns(atrule.params)}${atrule.raws.between||''}${atrule.nodes?`{${atrule.nodes.map(node => toString(node)).join('')}${atrule.raws.after||''}}`:';'}`; 33 | } 34 | 35 | function ruleToString (rule) { 36 | return `${rule.raws.before||''}${replaceVars(rule.selector)}${rule.raws.between||''}{${rule.nodes.map(node => toString(node)).join('')}${rule.raws.after||''}}`; 37 | } 38 | 39 | function declToString (decl) { 40 | return `${decl.raws.before || ''}${decl.prop}${decl.raws.between || ':'}${replaceVarsAndFns(decl.value)};`; 41 | } 42 | 43 | function commentToString (comment) { 44 | return `${comment.raws.before}/*${comment.raws.left}${comment.text}${comment.raws.right}*/`; 45 | } 46 | 47 | builder( 48 | toString(root) 49 | ); 50 | } 51 | 52 | export function parseExample(string) { 53 | return postcss().process(string, { 54 | stringifier: postcssToHTML 55 | }).css; 56 | } 57 | -------------------------------------------------------------------------------- /tasks/populate-db.mjs: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { URL } from 'url'; 3 | import fs from 'fs/promises'; 4 | import supportedBrowsersFromMdn from '../utils/supported-browsers-from-mdn.mjs'; 5 | import { baselineData } from '../utils/baseline-data.mjs'; 6 | import applyBrowserOverrides from '../utils/apply-browser-overrides.mjs'; 7 | import { applyWebFeaturesData } from '../utils/web-features-data.mjs'; 8 | 9 | const __dirname = new URL('.', import.meta.url).pathname; 10 | const settingsPath = path.resolve(__dirname, '../cssdb.settings.json'); 11 | const cssdb = await fs.readFile(settingsPath, 'utf8').then(JSON.parse); 12 | 13 | cssdb.forEach(applyWebFeaturesData); 14 | 15 | cssdb.forEach(feature => { 16 | feature.browser_support = {}; 17 | let browser_support = {}; 18 | if (feature.mdn_path) { 19 | browser_support = supportedBrowsersFromMdn(feature.mdn_path, feature); 20 | } 21 | 22 | browser_support = applyBrowserOverrides( 23 | feature.id, 24 | browser_support, 25 | feature.browser_support_overrides 26 | ); 27 | 28 | const browsers = Object.keys(browser_support).sort(); 29 | browsers.forEach(browser => { 30 | feature.browser_support[browser] = browser_support[browser]; 31 | }); 32 | 33 | const [vendors_implementations, interoperable_at] = baselineData(feature); 34 | feature.vendors_implementations = vendors_implementations; 35 | feature.interoperable_at = interoperable_at; 36 | }); 37 | 38 | const cleanDB = cssdb.map( 39 | ({ 40 | mdn_path, 41 | allow_partial_implementation, 42 | mdn_count_prefixed_as_supported, 43 | browser_support_overrides, 44 | // The above are discarded 45 | ...properties 46 | }) => { 47 | let feature = {}; 48 | let sortedKeys = Object.keys(properties).sort((a, b) => { 49 | const fixedKeyPositions = ['id', 'title', 'description', 'specification', 'stage']; 50 | const aIndex = fixedKeyPositions.indexOf(a); 51 | const bIndex = fixedKeyPositions.indexOf(b); 52 | 53 | if (aIndex === -1 && bIndex === -1) { 54 | return a.localeCompare(b); 55 | } 56 | 57 | if (aIndex === -1) { 58 | return 1; 59 | } 60 | 61 | if (bIndex === -1) { 62 | return -1; 63 | } 64 | 65 | return aIndex - bIndex; 66 | }); 67 | sortedKeys.forEach(key => { 68 | feature[key] = properties[key]; 69 | }); 70 | 71 | return feature; 72 | } 73 | ); 74 | 75 | const newCSSDB = `${JSON.stringify(cleanDB, null, 2)}\n`; 76 | const esmCSSDB = `export default ${newCSSDB}`; 77 | 78 | await Promise.all([ 79 | fs.writeFile(path.resolve(__dirname, '../cssdb.json'), newCSSDB), 80 | fs.writeFile(path.resolve(__dirname, '../cssdb.mjs'), esmCSSDB) 81 | ]); 82 | -------------------------------------------------------------------------------- /public/images/css.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/images/browsers/edge.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/components/feature-polyfill.mjs: -------------------------------------------------------------------------------- 1 | import { escapeHTML, html } from "../util/html.mjs"; 2 | 3 | const presetEnvPlugins = [ 4 | 'all-property', 5 | 'any-link-pseudo-class', 6 | 'blank-pseudo-class', 7 | 'break-properties', 8 | 'cascade-layers', 9 | 'case-insensitive-attributes', 10 | 'clamp', 11 | 'color-function', 12 | 'color-functional-notation', 13 | 'color-mix', 14 | 'custom-media-queries', 15 | 'custom-properties', 16 | 'custom-selectors', 17 | 'dir-pseudo-class', 18 | 'display-two-values', 19 | 'double-position-gradients', 20 | 'exponential-functions', 21 | 'float-clear-logical-values', 22 | 'focus-visible-pseudo-class', 23 | 'focus-within-pseudo-class', 24 | 'font-format-keywords', 25 | 'font-variant-property', 26 | 'gamut-mapping', 27 | 'gap-properties', 28 | 'gradients-interpolation-method', 29 | 'has-pseudo-class', 30 | 'hexadecimal-alpha-notation', 31 | 'hwb-function', 32 | 'ic-unit', 33 | 'image-set-function', 34 | 'is-pseudo-class', 35 | 'lab-function', 36 | 'logical-properties-and-values', 37 | 'logical-resize', 38 | 'logical-viewport-units', 39 | 'media-queries-aspect-ratio-number-values', 40 | 'media-query-ranges', 41 | 'nested-calc', 42 | 'nesting-rules', 43 | 'not-pseudo-class', 44 | 'oklab-function', 45 | 'opacity-percentage', 46 | 'overflow-property', 47 | 'overflow-wrap-property', 48 | 'place-properties', 49 | 'prefers-color-scheme-query', 50 | 'rebeccapurple-color', 51 | 'relative-color-syntax', 52 | 'scope-pseudo-class', 53 | 'stepped-value-functions', 54 | 'system-ui-font-family', 55 | 'text-decoration-shorthand', 56 | 'trigonometric-functions', 57 | 'unset-value', 58 | ]; 59 | 60 | export function renderFeaturePolyfill(polyfill, id, title) { 61 | const repoUrl = polyfill.link.includes('csstools/postcss-plugins') ? 'https://github.com/csstools/postcss-plugins' : polyfill.link; 62 | const starImage = `https://img.shields.io/github/stars/${polyfill.link.split('/').slice(3, 5).join('/')}.svg?style=social`; 63 | const isGithub = repoUrl.includes('github.com'); 64 | const isBundled = presetEnvPlugins.includes(id); 65 | 66 | return html` 67 |
  • 68 | 69 | ${polyfill.type} 70 | for ${escapeHTML(title)} 71 | 72 | 73 | ${isGithub ? (html` 74 | 75 | 76 | GitHub stars 81 | 82 | ` 83 | ) : ''} 84 | 85 | ${(polyfill.type === "PostCSS Plugin" && isBundled) ? ( 86 | html` 87 | (bundled with Preset Env) 88 | ` 89 | ) : ''} 90 |
  • `; 91 | } 92 | -------------------------------------------------------------------------------- /public/images/cssdb.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cssdb", 3 | "version": "8.5.2", 4 | "type": "module", 5 | "description": "A list of CSS features and their positions in the process of becoming implemented web standards", 6 | "license": "MIT-0", 7 | "contributors": [ 8 | { 9 | "name": "Antonio Laguna", 10 | "email": "antonio@laguna.es", 11 | "url": "https://antonio.laguna.es" 12 | }, 13 | { 14 | "name": "Romain Menke", 15 | "email": "romainmenke@gmail.com" 16 | }, 17 | { 18 | "name": "Jonathan Neal", 19 | "email": "jonathantneal@hotmail.com" 20 | } 21 | ], 22 | "funding": [ 23 | { 24 | "type": "opencollective", 25 | "url": "https://opencollective.com/csstools" 26 | }, 27 | { 28 | "type": "github", 29 | "url": "https://github.com/sponsors/csstools" 30 | } 31 | ], 32 | "repository": "csstools/cssdb", 33 | "homepage": "https://github.com/csstools/cssdb#readme", 34 | "bugs": "https://github.com/csstools/cssdb/issues", 35 | "main": "cssdb.json", 36 | "module": "cssdb.mjs", 37 | "files": [ 38 | "cssdb.json", 39 | "cssdb.mjs" 40 | ], 41 | "exports": { 42 | ".": { 43 | "import": "./cssdb.mjs", 44 | "require": "./cssdb.json", 45 | "default": "./cssdb.json" 46 | } 47 | }, 48 | "scripts": { 49 | "start": "node ./tasks/preview-site.mjs", 50 | "prestart": "npm run build", 51 | "build": "node ./tasks/render-site.mjs", 52 | "prebuild": "npm run preparesite", 53 | "prepublishOnly": "npm run populatedb", 54 | "populatedb": "node tasks/populate-db.mjs", 55 | "create-badges": "node tasks/write-stage-badges.mjs && node tasks/write-baseline-badges.mjs", 56 | "preparesite": "npm run populatedb && npm run create-badges && npm run buildcss", 57 | "buildcss": "postcss src/styles/style.css -d dist/styles -m", 58 | "test": "npm run test:css && npm run test:json", 59 | "test:css": "stylelint src/styles/style.css", 60 | "test:json": "node tasks/test.cjs", 61 | "test:doc-links": "node tasks/check-doc-links.mjs" 62 | }, 63 | "devDependencies": { 64 | "@mdn/browser-compat-data": "^7.2.1", 65 | "browserslist": "^4.28.1", 66 | "glob": "^13.0.0", 67 | "postcss": "^8.5.6", 68 | "postcss-cli": "^11.0.1", 69 | "postcss-preset-env": "^10.5.0", 70 | "semver": "^7.7.3", 71 | "stylelint": "^16.26.1", 72 | "stylelint-config-standard": "^39.0.1", 73 | "web-features": "^3.11.1" 74 | }, 75 | "stylelint": { 76 | "extends": "stylelint-config-standard", 77 | "rules": { 78 | "property-no-unknown": [ 79 | true, 80 | { 81 | "ignoreProperties": [ 82 | "font-smoothing" 83 | ] 84 | } 85 | ], 86 | "selector-class-pattern": null, 87 | "no-descending-specificity": null, 88 | "value-keyword-case": null 89 | } 90 | }, 91 | "keywords": [ 92 | "css", 93 | "features", 94 | "specifications", 95 | "stages" 96 | ], 97 | "volta": { 98 | "node": "22.11.0" 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/components/feature.mjs: -------------------------------------------------------------------------------- 1 | import { baselineStatus } from '../../utils/baseline-status.mjs'; 2 | import { parseExample } from '../../utils/feature-example.js'; 3 | import { backTicksToCodeTags, escapeHTML, html } from '../util/html.mjs'; 4 | import { renderFeatureSupportStats } from './feature-support-stats.mjs'; 5 | import { renderFeaturePolyfills } from './feature-polyfills.mjs'; 6 | 7 | const stages = [ 8 | 'stage-0-aspirational', 9 | 'stage-1-experimental', 10 | 'stage-2-allowable', 11 | 'stage-3-embraced', 12 | 'stage-4-standardized', 13 | ]; 14 | 15 | export function renderFeature(feature) { 16 | 17 | const imageName = `/images/stages/stage-${feature.stage}.svg`; 18 | const badge = `/images/badges/${feature.id}.svg`; 19 | const baselineBadge = `/images/badges-baseline/${feature.id}.svg`; 20 | 21 | const cleanTitle = feature.title.replace(/`/g, ''); 22 | 23 | return html` 24 |
    25 |
    26 | 29 |

    30 | 31 | ${backTicksToCodeTags(escapeHTML(feature.title))} 32 | 33 |

    34 |

    35 | ${backTicksToCodeTags(escapeHTML(feature.description))} 36 |

    37 |
    38 |

    39 | 43 | 52 | 53 | ${feature.docs?.mdn ? ( 54 | html` 60 | 68 | ` 69 | ) : ''} 70 | 74 | 82 | 83 | 87 | 95 | 96 |

    97 |
    ${parseExample(feature.example)}
    98 | 99 | ${renderFeatureSupportStats(cleanTitle, feature)} 100 | ${renderFeaturePolyfills(feature.polyfills, feature.id, cleanTitle)} 101 |
    `; 102 | } 103 | -------------------------------------------------------------------------------- /public/images/browsers/and_uc.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to make participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting Jonathan Neal . All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /tasks/preview-site.mjs: -------------------------------------------------------------------------------- 1 | import http from 'http'; 2 | import { promises as fsp } from 'fs'; 3 | import { spawn } from 'child_process'; 4 | import postcss from 'postcss'; 5 | import postcssPresetEnv from 'postcss-preset-env'; 6 | import path from 'path'; 7 | 8 | const distPath = new URL('../dist', import.meta.url).href; 9 | 10 | (async () => { 11 | const requestListener = async function (req, res) { 12 | const parsedUrl = new URL(req.url, 'http://localhost:8080'); 13 | const pathname = parsedUrl.pathname; 14 | 15 | switch (pathname) { 16 | case '': 17 | case '/': 18 | await renderSite().then((html) => { 19 | res.setHeader('Content-type', 'text/html'); 20 | res.writeHead(200); 21 | res.end(html); 22 | }).catch((err) => { 23 | console.log(err); 24 | res.setHeader('Content-type', 'text/plain'); 25 | res.writeHead(500); 26 | res.end('Internal server error'); 27 | }); 28 | break; 29 | case '/favicon.ico': 30 | res.setHeader('Content-type', 'image/vnd.microsoft.icon'); 31 | res.writeHead(200); 32 | res.end(await fsp.readFile(new URL('../dist/favicon.ico', import.meta.url))); 33 | break; 34 | case '/styles/style.css': 35 | res.setHeader('Content-type', 'text/css'); 36 | res.writeHead(200); 37 | 38 | const css = await fsp.readFile(new URL('../src/styles/style.css', import.meta.url), 'utf8'); 39 | const processesCSS = await postcss([ 40 | postcssPresetEnv({ 41 | stage: 0, 42 | enableClientSidePolyfills: true 43 | }) 44 | ]).process(css, { 45 | from: new URL('../src/styles/style.css', import.meta.url).href, 46 | map: { inline: true } 47 | }); 48 | 49 | res.end(processesCSS.css); 50 | break; 51 | 52 | default: 53 | if (pathname.startsWith('/images/')) { 54 | const imageURL = new URL(path.join('..', 'dist', pathname), import.meta.url); 55 | if (!imageURL.href.startsWith(distPath)) { 56 | res.setHeader('Content-type', 'text/plain'); 57 | res.writeHead(500); 58 | res.end('Internal server error'); 59 | return; 60 | } 61 | 62 | if (pathname.endsWith('.svg')) { 63 | res.setHeader('Content-type', 'image/svg+xml'); 64 | } else if (pathname.endsWith('.png')) { 65 | res.setHeader('Content-type', 'image/png'); 66 | } else if (pathname.endsWith('.jpg') || pathname.endsWith('.jpeg')) { 67 | res.setHeader('Content-type', 'image/jpeg'); 68 | } else { 69 | res.setHeader('Content-type', 'text/plain'); 70 | res.writeHead(500); 71 | res.end('Internal server error'); 72 | return; 73 | } 74 | 75 | res.writeHead(200); 76 | res.end(await fsp.readFile(new URL(path.join('..', 'dist', pathname), import.meta.url))); 77 | break; 78 | } 79 | 80 | res.setHeader('Content-type', 'text/plain'); 81 | res.writeHead(404); 82 | res.end('Not found'); 83 | break; 84 | } 85 | }; 86 | 87 | const server = http.createServer(requestListener); 88 | server.listen(8080); 89 | console.log('visit : http://localhost:8080'); 90 | })(); 91 | 92 | function renderSite() { 93 | return new Promise((resolve, reject) => { 94 | try { 95 | const child = spawn('node', ['./tasks/render-site-to-stdout.mjs']); 96 | 97 | let stdout = ""; 98 | let stderr = ""; 99 | 100 | child.stdout.setEncoding('utf8'); 101 | child.stdout.on('data', function (data) { 102 | stdout += data.toString(); 103 | }); 104 | 105 | child.stderr.setEncoding('utf8'); 106 | child.stderr.on('data', function (data) { 107 | stderr += data.toString(); 108 | }); 109 | 110 | child.on('close', function (code) { 111 | if (stderr.length) { 112 | reject(new Error(stderr)); 113 | return; 114 | } 115 | 116 | resolve(stdout); 117 | }); 118 | } catch (err) { 119 | reject(err); 120 | } 121 | }); 122 | } 123 | -------------------------------------------------------------------------------- /tasks/test.cjs: -------------------------------------------------------------------------------- 1 | // internal tooling 2 | const fs = require('fs').promises; 3 | const path = require('path'); 4 | const assert = require('assert'); 5 | 6 | // symbols for passing and failure 7 | const passSymbol = '\x1b[32m✔\x1b[0m'; 8 | const failSymbol = '\x1b[31m✖\x1b[0m'; 9 | 10 | // path to cssdb.json 11 | const cssdbJSON = path.join(__dirname, '../cssdb.json'); 12 | 13 | // whether to fix the file if possible 14 | const isToBeFixed = process.argv.indexOf('--fix') !== -1; 15 | 16 | // test features.json 17 | fs.readFile(cssdbJSON).then((data) => { 18 | return JSON.parse(data); 19 | }).then(allHaveRequiredData).then( 20 | length => console.log(`${passSymbol} all ${length} features are valid.`), 21 | error => console.log(`${failSymbol} something did not validate.\n → ${error}`) 22 | ); 23 | 24 | // test all features for validity 25 | function allHaveRequiredData (features) { 26 | if (isToBeFixed) { 27 | fixFeaturesOrdering(features); 28 | } 29 | 30 | assert.deepStrictEqual( 31 | getFeatureIds(features), 32 | getFeatureIds(features).sort(), 33 | ) 34 | 35 | features.forEach(hasRequiredData); 36 | 37 | return fs.writeFile( 38 | cssdbJSON, 39 | `${JSON.stringify(features, null, ' ')}\n` 40 | ).then( 41 | () => features.length 42 | ); 43 | } 44 | 45 | // test a feature for validity 46 | function hasRequiredData (feature) { 47 | const title = Object(feature).title || 'Unknown Feature'; 48 | const keys = Object.keys(Object(feature)); 49 | const ordering = ['id', 'title', 'description', 'specification', 'stage']; 50 | const polyfillOrdering = ['type', 'link']; 51 | 52 | const hasRequiredFields = keys.slice(0, 5).join() === ordering.join(); 53 | const hasValidStage = feature.stage === -1 || feature.stage === 0 || feature.stage === 1 || feature.stage === 2 || feature.stage === 3 || feature.stage === 4; 54 | const hasOrderlyAdditionalFields = keys.slice(5).join() === keys.slice(5).sort().join(); 55 | const hasOrderlyPolyfills = !Array.isArray(feature.polyfills) || feature.polyfills.every( 56 | polyfill => Object.keys(Object(polyfill)).join() === polyfillOrdering.join() 57 | ); 58 | 59 | if (isToBeFixed) { 60 | if (!hasRequiredFields || !hasOrderlyAdditionalFields) { 61 | fixFeatureOrdering(feature, ordering); 62 | } 63 | 64 | if (!hasOrderlyPolyfills) { 65 | fixFeatureOrdering(feature.polyfills, polyfillOrdering); 66 | } 67 | } else if (!hasRequiredFields) { 68 | throw validationError('MISSING OR UNORDERLY REQUIRED FIELDS', title, keys.slice(0, 5).join(', ')); 69 | } else if (!hasOrderlyAdditionalFields) { 70 | throw validationError('UNORDERLY ADDITIONAL FIELDS', title, keys.slice(5).join(', ')); 71 | } else if (!hasOrderlyPolyfills) { 72 | validationError('UNORDERLY POLYFILLS FIELDS', title, polyfillOrdering.join(', ')) 73 | } 74 | 75 | if (!hasValidStage) { 76 | throw validationError('INVALID STAGE', title, feature.stage); 77 | } 78 | } 79 | 80 | // report a validation error 81 | function validationError (issue, title, notice) { 82 | return `${issue}: ${title}${notice ? ` (${notice})` : ''}` 83 | } 84 | 85 | // get feature ids 86 | function getFeatureIds (features) { 87 | return features.slice(0).map(feature => feature.id); 88 | } 89 | 90 | // fixes all feature ordering 91 | function fixFeaturesOrdering (features) { 92 | const sorting = ({ id: a }, { id: b }) => a < b ? -1 : a > b ? 1 : 0; 93 | 94 | features.sort(sorting); 95 | } 96 | 97 | 98 | // fixes a feature ordering 99 | function fixFeatureOrdering (object, order, error) { 100 | const clone = {}; 101 | const hasEveryKey = order.every(key => key in object); 102 | const keys = Object.keys(object); 103 | const additionalKeys = keys.filter(key => order.indexOf(key) === -1).sort(); 104 | 105 | if (hasEveryKey) { 106 | keys.forEach( 107 | key => { 108 | clone[key] = object[key]; 109 | 110 | delete object[key]; 111 | } 112 | ) 113 | 114 | order.concat(additionalKeys).forEach( 115 | key => { 116 | object[key] = clone[key]; 117 | } 118 | ); 119 | } else { 120 | throw error; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /STAGES.md: -------------------------------------------------------------------------------- 1 | ## The Staging Process 2 | 3 | Staging processes allow developers to accomplish real things and get involved 4 | in the creation of standards, testing, feedback, and new use cases. 5 | 6 | This staging process reflects the real-life stability of new CSS features. 7 | 8 | You can read an [inside view of the CSSWG] to learn about the official 9 | (and unofficial) development stages of CSS specifications. In reality, 10 | specifications and browser implementations happen out of sync. For example, 11 | there have been stable CSS features missing in all browsers, while other CSS 12 | features developed outside the [W3C] have appeared in browsers. This is too 13 | ambiguous for the web development community, and a more accountable process 14 | is desired. 15 | 16 | ### Stage 0: Aspirational 17 | 18 | 19 | 20 | > “This is a silly idea.” 21 | 22 | An **Unofficial Draft** or **Editor’s Draft** [championed] by a 23 | [W3C Working Group] Member. It should be considered highly unstable and subject 24 | to change. Stage 0 features are open to ideas and discussion, but may not be 25 | considered serious. 26 | 27 | --- 28 | 29 | ### Stage 1: Experimental 30 | 31 | 32 | 33 | > “This idea might not be silly.” 34 | 35 | An **Editor’s Draft** or early **Working Draft** [championed] by a 36 | [W3C Working Group]. It should be considered highly unstable and subject to 37 | change. Stage 1 features are recognized as a real problem, but they may not be 38 | tied to any particular solution. 39 | 40 | --- 41 | 42 | ### Stage 2: Allowable 43 | 44 | 45 | 46 | > “This idea is not silly.” 47 | 48 | A **Working Draft** [championed] by a [W3C Working Group]. It should be 49 | considered relatively unstable and subject to change. Stage 2 features are tied 50 | to a particular way of solving a problem. 51 | 52 | --- 53 | 54 | ### Stage 3: Embraced 55 | 56 | 57 | 58 | > “This idea is becoming part of the web.” 59 | 60 | A **Candidate Recommendation** [championed] by a [W3C Working Group], usually 61 | implemented by at least 2 [recognized browser vendors], possibly behind a flag. 62 | It should be considered stable and subject to little change. Stage 3 features 63 | will likely become a standard. 64 | 65 | --- 66 | 67 | ### Stage 4: Standardized 68 | 69 | 70 | 71 | > “This idea is part of the web.” 72 | 73 | A **Recommendation** [championed] by the [W3C]. It should be implemented by all 74 | [recognized browser vendors]. Stage 4 features are web standards. 75 | 76 | --- 77 | 78 | ### Rejected 79 | 80 | 81 | 82 | > “I had no idea what I was doing.” 83 | 84 | Any specification that has been rejected or neglected by its editor, or 85 | formally rejected by a [W3C Working Group]. 86 | 87 | --- 88 | 89 | ## Terminology 90 | 91 | ### Recognized Browser Vendors 92 | 93 | Recognized browser vendors include, in alphabetical order; Apple (Safari/Webkit), Google (Chrome/Chromium) and Mozilla (Firefox/Gecko). 94 | 95 | ### What is a champion? 96 | 97 | A champion is the person or group responsible for advocating a new feature to 98 | completion, performing the legwork necessary to ensure the concerns of 99 | interested parties are identified and incorporated into the proposal. 100 | 101 | [championed]: #what-is-a-champion 102 | [hosted]: #what-is-a-champion 103 | [inside view of the CSSWG]: https://fantasai.inkedblade.net/weblog/2011/inside-csswg/process 104 | [recognized browser vendors]: #recognized-browser-vendors 105 | [W3C]: https://www.w3.org/ 106 | [W3C Working Group]: https://wiki.csswg.org/spec 107 | -------------------------------------------------------------------------------- /utils/supported-browsers-from-mdn.mjs: -------------------------------------------------------------------------------- 1 | import bcd from '@mdn/browser-compat-data' with { type: 'json' }; 2 | import semver from 'semver'; 3 | import { get } from './get.mjs'; 4 | import { MDNToBrowserlist } from './mdn-to-browserslist.mjs'; 5 | 6 | function getBrowsersFromFeature(mdnConfigPath, feature) { 7 | const mdnFeature = get(bcd, mdnConfigPath); 8 | if (!mdnFeature) { 9 | throw new Error(`Invalid mdn config path "${mdnConfigPath}" in feature "${feature.id}"`); 10 | } 11 | 12 | // We assume users also have autoprefixer. 13 | // If autoprefixer adds prefixes for this feature we count it as supported. 14 | const supportsPrefixes = Object(feature.mdn_count_prefixed_as_supported)[mdnConfigPath]; 15 | const result = {}; 16 | 17 | const { __compat: { support } } = mdnFeature; 18 | 19 | Object.keys(support).forEach(browserKey => { 20 | const browserSupport = support[browserKey]; 21 | let version; 22 | 23 | if (Array.isArray(browserSupport)) { 24 | const versions = browserSupport.sort((a, b) => { 25 | const aa = semver.coerce(a.version_added); 26 | const bb = semver.coerce(b.version_added); 27 | if (!aa || !bb) { 28 | return 0; 29 | } 30 | 31 | return semver.compare(aa, bb); 32 | }); 33 | 34 | version = versions.find(browserEntry => { 35 | const hasAlternativeName = typeof browserEntry.alternative_name !== 'undefined'; 36 | const isPrefixed = typeof browserEntry.prefix !== 'undefined'; 37 | const isAllowedPrefix = isPrefixed && browserEntry.prefix !== '-khtml-' && !browserEntry.partial_implementation; 38 | const hasFlags = Array.isArray(browserEntry.flags); 39 | 40 | if (hasAlternativeName || hasFlags) { 41 | return false; 42 | } 43 | 44 | if (browserEntry.partial_implementation) { 45 | return false; 46 | } 47 | 48 | if (supportsPrefixes && isAllowedPrefix) { 49 | return true; 50 | } 51 | 52 | return !isPrefixed; 53 | })?.version_added; 54 | } else { 55 | const hasFlags = Array.isArray(browserSupport.flags); 56 | version = browserSupport.version_added; 57 | 58 | if (hasFlags) { 59 | return; 60 | } 61 | 62 | if (browserSupport.partial_implementation) { 63 | return false; 64 | } 65 | 66 | const isPrefixed = typeof browserSupport.prefix !== 'undefined'; 67 | const isAllowedPrefix = isPrefixed && browserSupport.prefix !== '-khtml-' && !browserSupport.partial_implementation; 68 | if (isPrefixed && !(supportsPrefixes && isAllowedPrefix)) { 69 | return; 70 | } 71 | 72 | if (browserSupport.alternative_name) { 73 | return; 74 | } 75 | } 76 | 77 | if (version) { 78 | // see : https://github.com/mdn/browser-compat-data/issues/17336 79 | // Ranged values means that a feature may have been added in a version earlier than "37", 80 | // but we don't know exactly when. 81 | if (version.startsWith('≤')) { 82 | version = version.substring(1); 83 | } 84 | 85 | const browser = MDNToBrowserlist(browserKey); 86 | result[browser] = version; 87 | } 88 | }); 89 | 90 | return result; 91 | } 92 | 93 | export default function supportedBrowsersFromMdn(path, feature) { 94 | const result = {}; 95 | 96 | const paths = Array.isArray(path) ? path : [path]; 97 | if (!paths.length) { 98 | return result; 99 | } 100 | 101 | const supports = paths.map(mdnPath => getBrowsersFromFeature(mdnPath, feature)); 102 | 103 | Object.keys(supports[0]).forEach(browserKey => { 104 | const isInAllFeatures = supports.every(featureSupport => typeof featureSupport[browserKey] !== 'undefined'); 105 | 106 | if (isInAllFeatures) { 107 | // Sort by descending version and get the first 108 | const [version] = supports.map(featureSupport => featureSupport[browserKey]).filter((x) => { 109 | return !!semver.coerce(x); 110 | }).sort((a, b) => { 111 | const aa = semver.coerce(a); 112 | const bb = semver.coerce(b); 113 | if (!aa || !bb) { 114 | return 0; 115 | } 116 | 117 | return semver.compare(bb, aa); // reverse sort 118 | }); 119 | result[browserKey] = version; 120 | } 121 | }); 122 | 123 | return result; 124 | } 125 | -------------------------------------------------------------------------------- /utils/baseline-status.mjs: -------------------------------------------------------------------------------- 1 | export function baselineStatus(feature) { 2 | if (!feature.vendors_implementations) { 3 | return ['Experimental', null]; 4 | } 5 | 6 | if (!feature.interoperable_at) { 7 | return ['Limited availability', null]; 8 | } 9 | 10 | const interoperable_at = new Date(); 11 | interoperable_at.setTime(feature.interoperable_at * 1000); 12 | const thirty_months_ago = new Date(); 13 | thirty_months_ago.setMonth(thirty_months_ago.getMonth() - 30); 14 | if (feature.interoperable_at > (thirty_months_ago.getTime() / 1000)) { 15 | return [`Newly available (${interoperable_at.getFullYear()})`, interoperable_at.getFullYear()]; 16 | } 17 | 18 | const earliest_baseline_low_date = (new Date("2015-07-29")).getTime() / 1000; 19 | if (earliest_baseline_low_date >= feature.interoperable_at) { 20 | return [`Widely available`, null]; 21 | } 22 | 23 | return [`Widely available (${interoperable_at.getFullYear()})`, interoperable_at.getFullYear()]; 24 | } 25 | 26 | export function baselineIcon(status, year) { 27 | if (status === 'Experimental') { 28 | return `${status}`; 29 | } 30 | 31 | if (status === 'Limited availability') { 32 | return `${status}`; 33 | } 34 | 35 | if (status.startsWith('Newly available')) { 36 | return `${status}${year}`; 37 | } 38 | 39 | if (status === 'Widely available') { 40 | return `${status}`; 41 | } 42 | 43 | return `${status}${year}`; 44 | } 45 | -------------------------------------------------------------------------------- /public/images/browsers/android.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/browsers/safari.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/components/stages.mjs: -------------------------------------------------------------------------------- 1 | import { html } from "../util/html.mjs"; 2 | 3 | export function renderStages() { 4 | // TODO : this is copy/paste from the previous Astro output. 5 | // Astro had a way to convert markdown to html. 6 | // The original content can be found in "STAGES.md" in the root of the repo. 7 | return html` 8 | 92 | `; 93 | } 94 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing to this database 2 | 3 | Pull requests are the most helpful contributions. 4 | 5 | I love folks who can add missing features and keep existing features up to date. 6 | A new feature only needs a **title**, **id**, **description**, and a link to 7 | its **specification**. 8 | 9 | Non-CSSWG members can still update CSS features by including **citations** to 10 | public notes that show how the CSSWG is advancing a feature. If you need 11 | further clarification, read [why we are doing this](#why-are-we-doing-this). 12 | 13 | ### Updating a feature 14 | 15 | Is one of these CSS features out-of-date? Open [`cssdb.settings.json`] and find 16 | the feature you want to update. It’s a JSON file, so make changes directly to 17 | the file, commit it, and then [make a pull request](#making-a-pull-request). 18 | 19 | ### Adding a new feature 20 | 21 | Is a CSS feature not listed here? Add the feature to [`cssdb.settings.json`]. 22 | Again, it’s a JSON file, so make changes directly to the JSON, commit it, and 23 | then [make a pull request](#making-a-pull-request). 24 | 25 | ### Making a Pull Request 26 | 27 | For best results, be sure your contributions make sense to everyone else. If 28 | you’re unfamiliar with git, consider the following workflow. 29 | 30 | 1. To begin; [fork this project] and then clone your fork locally 31 | ```bash 32 | # Clone your fork of this project 33 | git clone git@github.com:YOUR_USER/cssdb.git 34 | 35 | # Navigate to your fork of this project 36 | cd cssdb 37 | 38 | # Install the tools necessary for testing this project 39 | npm install 40 | 41 | # Assign the original repo to a remote called "upstream" 42 | git remote add upstream git@github.com:csstools/cssdb.git 43 | ``` 44 | 45 | 2. Create a branch for your feature or fix: 46 | ```bash 47 | # Move into a new branch for your feature 48 | git checkout -b feature/thing 49 | ``` 50 | ```bash 51 | # Move into a new branch for your fix 52 | git checkout -b fix/something 53 | ``` 54 | 55 | 3. If your code follows our practices, then push your feature branch: 56 | ```bash 57 | # Test your code 58 | npm test 59 | ``` 60 | ```bash 61 | # Push the branch for your new feature 62 | git push origin feature/thing 63 | ``` 64 | ```bash 65 | # Or, push the branch for your update 66 | git push origin update/something 67 | ``` 68 | 69 | --- 70 | 71 | ## Advanced Usage: How the JSON file looks 72 | 73 | The only fields you’ll see in [`cssdb.settings.json`] are, in order: 74 | 75 | - `id`: the feature shortname, similar to a specification [Shortname]. 76 | - `title`: the name of the feature. 77 | - `description`: a brief description of the feature. 78 | - `specification`: a link to the specification for the feature. 79 | - `stage`: the current [stage](README.md#staging-process) of the feature; where 80 | + `0` means **Stage 0** — *Aspirational*, 81 | + `1` means **Stage 1** — *Enthusiastic*, 82 | + `2` means **Stage 2** — *Experimental*, 83 | + `3` means **Stage 3** — *Allowable*, 84 | + `4` means **Stage 4** — *Embraced*, 85 | + `5` means **Stage 5** — *Standardized*, and 86 | + `-1` means **Rejected**. 87 | - `polyfills`: a list of polyfills used to simulate the feature; each including 88 | + `type`: the type of polyfill (e.g. *PostCSS*, *JS Library*), and 89 | + `link`: the URL to the repository for the polyfill. 90 | 91 | All contributions must follow the existing syntax and style of the JSON file, 92 | which; 93 | 94 | 1. Exists as `cssdb.settings.json`, and; 95 | 2. Includes at least the required fields; **title**, **description**, 96 | **specification**, and **stage** (which is `0` if you don’t know it). 97 | 98 | Did you write the specification you are submitting? It must; 99 | 100 | 1. Describe what the feature does in as few words as possible. 101 | 2. Describe why the feature exists in as few words as possible. 102 | 3. Describe how the feature and its parts operate as clearly and completely as 103 | possible. 104 | 105 | If you’re changing the **stage** of a feature, be sure to include a link along 106 | with your PR to prove the new position. 107 | 108 | --- 109 | 110 | ## Join the CSSWG 111 | 112 | Passionate and informed developers should consider joining the CSSWG. Read the 113 | [instructions for joining the CSSWG]. This is a very difficult to actually 114 | accomplish, so I welcome pull requests to update this section with some 115 | beginner-friendly instructions. 116 | 117 | ### Why are we doing this? 118 | 119 | The CSSWG doesn’t follow the [TC39 process]. How they operate [in theory](https://www.w3.org/Style/CSS/specs.en.html) versus [in real life](https://fantasai.inkedblade.net/weblog/2011/inside-csswg/) is unclear, and 120 | browsers 121 | [don’t necessarily follow their process anyway](https://www.chromestatus.com/feature/5753701012602880), 122 | so we have to discern what’s really going on ourselves. If we didn’t, we 123 | probably wouldn’t need this repository. 124 | 125 | [`cssdb.settings.json`]: cssdb.settings.json 126 | [fork this project]: fork 127 | [instructions for joining the CSSWG]: https://www.w3.org/2004/01/pp-impl/32061/instructions 128 | [staging process]: README.md#staging-process 129 | [TC39 process]: https://thefeedbackloop.xyz/tc39-a-process-sketch-stages-0-and-1/ 130 | [Section Heading ID]: https://tabatkins.github.io/bikeshed/#section-links 131 | [Shortname]: https://tabatkins.github.io/bikeshed/#metadata-shortname 132 | -------------------------------------------------------------------------------- /public/images/css-tools.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/images/css-tools-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/images/browsers/and_qq.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/browsers/firefox.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/styles/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-display: swap; 3 | font-family: "Avenir Next"; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: local("Avenir Next Medium"), local("AvenirNext-Medium"), url("https://d32ekf2zat2q3c.cloudfront.net/Client/AMN+Healthcare/fonts/AvenirNext-Medium.woff2") format("woff2"), url("https://d32ekf2zat2q3c.cloudfront.net/Client/AMN+Healthcare/fonts/AvenirNext-Medium.woff") format("woff"); 7 | } 8 | 9 | @font-face { 10 | font-display: swap; 11 | font-family: "Avenir Next"; 12 | font-style: italic; 13 | font-weight: 400; 14 | src: local("Avenir Next Medium Italic"), local("AvenirNext-MediumItalic"), url("https://d32ekf2zat2q3c.cloudfront.net/Client/AMN+Healthcare/fonts/AvenirNext-MediumItalic.woff2") format("woff2"), url("https://d32ekf2zat2q3c.cloudfront.net/Client/AMN+Healthcare/fonts/AvenirNext-MediumItalic.woff") format("woff"); 15 | } 16 | 17 | @font-face { 18 | font-display: swap; 19 | font-family: "Avenir Next"; 20 | font-style: normal; 21 | font-weight: 700; 22 | src: local("Avenir Next Bold"), local("AvenirNext-Bold"), url("https://d32ekf2zat2q3c.cloudfront.net/Client/AMN+Healthcare/fonts/AvenirNext-Bold.woff2") format("woff2"), url("https://d32ekf2zat2q3c.cloudfront.net/Client/AMN+Healthcare/fonts/AvenirNext-Bold.woff") format("woff"); 23 | } 24 | 25 | @font-face { 26 | font-display: swap; 27 | font-family: "Avenir Next"; 28 | font-style: italic; 29 | font-weight: 700; 30 | src: local("Avenir Next Bold Italic"), local("AvenirNext-BoldItalic"), url("https://d32ekf2zat2q3c.cloudfront.net/Client/AMN+Healthcare/fonts/AvenirNext-Bold.woff2") format("woff2"), url("https://d32ekf2zat2q3c.cloudfront.net/Client/AMN+Healthcare/fonts/AvenirNext-Bold.woff") format("woff"); 31 | } 32 | 33 | /* Document 34 | /* ========================================================================== */ 35 | 36 | html { 37 | --main-font: "Avenir Next", sans-serif; 38 | --code-font: "Source Code Pro", "Andale Mono WT", "Andale Mono", "Consolas", "Lucida Console", monospace; 39 | 40 | scroll-behavior: smooth; 41 | 42 | @media screen and (prefers-reduced-motion: reduce) { 43 | scroll-behavior: auto; 44 | } 45 | } 46 | 47 | *, 48 | ::before, 49 | ::after { 50 | box-sizing: border-box; 51 | } 52 | 53 | body { 54 | font: 400 100%/1.5 sans-serif; 55 | font-family: var(--main-font); 56 | -moz-osx-font-smoothing: grayscale; 57 | -webkit-font-smoothing: antialiased; 58 | font-smoothing: antialiased; 59 | letter-spacing: -.0013em; 60 | padding: 0 calc((100% - 960px) / 2); 61 | text-rendering: optimizeLegibility; 62 | text-size-adjust: 100%; 63 | transition: 400ms background-color, 400ms color; 64 | 65 | @media (width < 560px) { 66 | margin: 16px; 67 | } 68 | 69 | @media (width >= 560px) { 70 | margin: 32px 64px; 71 | } 72 | 73 | @media (prefers-color-scheme: dark) { 74 | background-color: #151515; 75 | color: #ececec; 76 | } 77 | } 78 | 79 | a { 80 | color: #07d; 81 | text-decoration: none; 82 | 83 | @media (prefers-color-scheme: dark) { 84 | color: #76bfff; 85 | } 86 | } 87 | 88 | aside, 89 | header, 90 | main, 91 | section { 92 | display: block; 93 | } 94 | 95 | code, 96 | pre { 97 | font-family: var(--code-font); 98 | font-size: 100%; 99 | font-weight: 600; 100 | } 101 | 102 | code { 103 | font-family: var(--code-font); 104 | font-size: 90%; 105 | letter-spacing: -.0125em; 106 | 107 | &::before { 108 | content: "‘"; 109 | font-family: var(--main-font); 110 | padding-inline-end: .125em; 111 | } 112 | 113 | &::after { 114 | content: "’"; 115 | font-family: var(--main-font); 116 | padding-inline-start: .125em; 117 | } 118 | } 119 | 120 | img { 121 | border: 0; 122 | } 123 | 124 | .sr-only { 125 | border: 0; 126 | /* stylelint-disable-next-line property-no-deprecated */ 127 | clip: rect(1px, 1px, 1px, 1px); 128 | clip-path: inset(50%); 129 | height: 1px; 130 | margin: -1px; 131 | overflow: hidden; 132 | padding: 0; 133 | position: absolute; 134 | white-space: nowrap; 135 | width: 1px; 136 | } 137 | 138 | /* Header 139 | /* ========================================================================== */ 140 | 141 | .cssdb-header-branding { 142 | height: 100px; 143 | margin-block-end: 36px; 144 | width: 220px; 145 | } 146 | 147 | .cssdb-header-title { 148 | font-size: 30px; 149 | line-height: 1; 150 | margin: 0 0 12px; 151 | } 152 | 153 | .cssdb-header-description { 154 | line-height: calc(18 / 16); 155 | max-width: 44em; 156 | text-wrap: balance; 157 | } 158 | 159 | /* Features 160 | /* ========================================================================== */ 161 | 162 | .cssdb-feature { 163 | margin-block-start: 80px; 164 | } 165 | 166 | .cssdb-feature-header { 167 | display: grid; 168 | gap: 5px 15px; 169 | grid: 170 | "stage title" 171 | "stage description" 172 | / 62px auto; 173 | } 174 | 175 | .cssdb-feature-stage { 176 | grid-area: stage; 177 | } 178 | 179 | .cssdb-feature-stage-image { 180 | height: 62px; 181 | width: 62px; 182 | display: block; 183 | } 184 | 185 | .cssdb-feature-heading { 186 | font-size: 1.17em; 187 | grid-area: title; 188 | margin: 0; 189 | 190 | & a { 191 | color: inherit; 192 | } 193 | } 194 | 195 | .cssdb-feature-description { 196 | grid-area: description; 197 | margin: 0; 198 | } 199 | 200 | .cssdb-feature-docs { 201 | font-size: 87.5%; 202 | margin-block-start: 20px; 203 | } 204 | 205 | .cssdb-feature-docs-link { 206 | font-weight: 700; 207 | text-transform: uppercase; 208 | } 209 | 210 | .cssdb-feature-polyfills { 211 | display: flex; 212 | flex-wrap: wrap; 213 | font-size: 87.5%; 214 | margin-top: 5px; 215 | 216 | &:dir(rtl) { 217 | flex-direction: column; 218 | } 219 | } 220 | 221 | .cssdb-feature-polyfill-list { 222 | list-style: none; 223 | margin: 0; 224 | padding: 0; 225 | 226 | &:dir(ltr) { 227 | margin-left: .5em; 228 | } 229 | } 230 | 231 | .cssdb-feature-polyfill-link { 232 | font-weight: 700; 233 | text-transform: uppercase; 234 | background-image: linear-gradient(currentColor 25%, transparent 50%); 235 | background-position: 200% 100%; 236 | background-repeat: no-repeat; 237 | background-size: 200% 2px; 238 | padding-block-end: 2px; 239 | transition: background-position 250ms; 240 | 241 | &:hover { 242 | background-position: 100% 100%; 243 | } 244 | } 245 | 246 | .cssdb-feature-polyfill-item { 247 | display: flex; 248 | flex-wrap: wrap; 249 | margin-block-end: 7px; 250 | column-gap: 5px; 251 | 252 | & .cssdb-feature-polyfill-stars { 253 | margin-inline-start: 3px; 254 | 255 | & img { 256 | height: 20px; 257 | } 258 | } 259 | 260 | & .cssdb-feature-polyfill-stars-minimal { 261 | overflow: hidden; 262 | 263 | & img { 264 | height: 20px; 265 | margin-inline-start: -56px; 266 | min-width: 57px; 267 | } 268 | } 269 | } 270 | 271 | .cssdb-feature-preset-env-badge-link { 272 | margin-inline-start: 10px; 273 | } 274 | 275 | /* Examples 276 | /* ========================================================================== */ 277 | 278 | .cssdb-feature-example { 279 | background-color: #f7f7f7; 280 | color: #727272; 281 | overflow: auto; 282 | padding: 20px 15px; 283 | transition: 400ms background-color, 400ms color; 284 | 285 | @media (prefers-color-scheme: dark) { 286 | background-color: #202020; 287 | color: #878787; 288 | } 289 | } 290 | 291 | .css-selector { 292 | color: #007500; 293 | 294 | @media (prefers-color-scheme: dark) { 295 | color: #14cc14; 296 | } 297 | } 298 | 299 | .css-property { 300 | color: #05a; 301 | 302 | @media (prefers-color-scheme: dark) { 303 | color: #498ef5; 304 | } 305 | } 306 | 307 | .css-atrule-name { 308 | color: #a00909; 309 | 310 | @media (prefers-color-scheme: dark) { 311 | color: #e95353; 312 | } 313 | } 314 | 315 | .css-comment { 316 | color: #727272; 317 | 318 | @media (prefers-color-scheme: dark) { 319 | color: #878787; 320 | } 321 | } 322 | 323 | .css-value, 324 | .css-atrule-params { 325 | color: #000; 326 | 327 | @media (prefers-color-scheme: dark) { 328 | color: #e8e8e8; 329 | } 330 | } 331 | 332 | .css-function { 333 | color: #0072d4; 334 | 335 | @media (prefers-color-scheme: dark) { 336 | color: #91b8f2; 337 | } 338 | } 339 | 340 | .css-string { 341 | color: #9f6500; 342 | 343 | @media (prefers-color-scheme: dark) { 344 | color: #ae7d4d; 345 | } 346 | } 347 | 348 | .css-var { 349 | color: #248400; 350 | 351 | @media (prefers-color-scheme: dark) { 352 | color: #55964d; 353 | } 354 | } 355 | 356 | /* Browsers 357 | /* ========================================================================== */ 358 | 359 | .cssdb-feature-support-stats { 360 | display: inline-flex; 361 | flex-wrap: wrap; 362 | margin-block-start: 5px; 363 | } 364 | 365 | .cssdb-browser { 366 | display: inline-block; 367 | height: 46px; 368 | margin-block-end: 12px; 369 | position: relative; 370 | width: 32px; 371 | 372 | &:empty { 373 | display: none; 374 | } 375 | 376 | &:not(:last-child) { 377 | margin-inline-end: 6px; 378 | } 379 | 380 | &::before { 381 | display: inline-block; 382 | height: 32px; 383 | line-height: 0; 384 | vertical-align: top; 385 | width: 32px; 386 | } 387 | } 388 | 389 | .cssdb-browser-version { 390 | display: block; 391 | font-size: 10px; 392 | line-height: 10px; 393 | padding-block-start: 4px; 394 | text-align: center; 395 | } 396 | 397 | .cssdb-browser--and_chr::after, 398 | .cssdb-browser--and_ff::after, 399 | .cssdb-browser--and_qq::after, 400 | .cssdb-browser--kaios::after, 401 | .cssdb-browser--and_uc::after { 402 | background-color: #fff; 403 | border-radius: 50%; 404 | box-shadow: 0 0 0 2px #8bc34a; 405 | content: url("/images/browsers/and.svg"); 406 | display: block; 407 | height: 12px; 408 | inset-block-end: 14px; 409 | inset-inline-end: -2px; 410 | line-height: 0; 411 | padding: 2px; 412 | position: absolute; 413 | width: 12px; 414 | z-index: 1; 415 | 416 | @media (prefers-color-scheme: dark) { 417 | background-color: #111; 418 | } 419 | } 420 | 421 | .cssdb-browser--ie_mob::after, 422 | .cssdb-browser--ios_saf::after, 423 | .cssdb-browser--op_mini::after, 424 | .cssdb-browser--op_mob::after { 425 | background-color: #fff; 426 | border-radius: 50%; 427 | box-shadow: 0 0 0 2px #474748; 428 | content: url("/images/browsers/mob.svg"); 429 | display: block; 430 | height: 12px; 431 | inset-block-end: 14px; 432 | inset-inline-end: -2px; 433 | line-height: 0; 434 | padding: 2px; 435 | position: absolute; 436 | width: 12px; 437 | z-index: 1; 438 | 439 | @media (prefers-color-scheme: dark) { 440 | background-color: #111; 441 | content: url("/images/browsers/mob@dark.svg"); 442 | } 443 | } 444 | 445 | .cssdb-browser--and_uc::before, 446 | .cssdb-browser--kaios::before { 447 | content: url("/images/browsers/and_uc.svg"); 448 | } 449 | 450 | .cssdb-browser--and_qq::before { 451 | content: url("/images/browsers/and_qq.svg"); 452 | } 453 | 454 | .cssdb-browser--android::before { 455 | background-image: url("/images/browsers/android.png"); 456 | background-size: contain; 457 | content: ""; 458 | } 459 | 460 | .cssdb-browser--baidu::before { 461 | content: url("/images/browsers/baidu.svg"); 462 | 463 | @media (prefers-color-scheme: dark) { 464 | content: url("/images/browsers/baidu@dark.svg"); 465 | } 466 | } 467 | 468 | .cssdb-browser--bb::before { 469 | content: url("/images/browsers/bb.svg"); 470 | 471 | @media (prefers-color-scheme: dark) { 472 | content: url("/images/browsers/bb@dark.svg"); 473 | } 474 | } 475 | 476 | .cssdb-browser--chrome::before, 477 | .cssdb-browser--and_chr::before { 478 | content: url("/images/browsers/chrome.svg"); 479 | } 480 | 481 | .cssdb-browser--edge::before { 482 | content: url("/images/browsers/edge.svg"); 483 | } 484 | 485 | .cssdb-browser--firefox::before, 486 | .cssdb-browser--and_ff::before { 487 | content: url("/images/browsers/firefox.svg"); 488 | } 489 | 490 | .cssdb-browser--ie::before, 491 | .cssdb-browser--ie_mob::before { 492 | content: url("/images/browsers/ie.svg"); 493 | } 494 | 495 | .cssdb-browser--ios_saf::before { 496 | content: url("/images/browsers/ios_saf.svg"); 497 | } 498 | 499 | .cssdb-browser--opera::before, 500 | .cssdb-browser--op_mini::before, 501 | .cssdb-browser--op_mob::before { 502 | content: url("/images/browsers/opera.svg"); 503 | } 504 | 505 | .cssdb-browser--safari::before { 506 | content: url("/images/browsers/safari.svg"); 507 | } 508 | 509 | .cssdb-browser--samsung::before { 510 | content: url("/images/browsers/samsung.svg"); 511 | } 512 | 513 | .cssdb-browser--oculus::before { 514 | content: url("/images/browsers/oculus.svg"); 515 | 516 | @media (prefers-color-scheme: dark) { 517 | filter: invert(1); 518 | } 519 | } 520 | 521 | .cssdb-browser--webview_ios::before { 522 | content: url("/images/browsers/webview_ios.svg"); 523 | } 524 | 525 | .cssdb-browser--webview_android::before { 526 | content: url("/images/browsers/webview_android.svg"); 527 | } 528 | 529 | /* Process 530 | /* ========================================================================== */ 531 | 532 | .cssdb-process { 533 | margin-block: 90px; 534 | 535 | & h2 { 536 | box-shadow: inset 0 -1px 0 0 #eaecef; 537 | font-size: 150%; 538 | padding-block-end: 10px; 539 | 540 | @media (prefers-color-scheme: dark) { 541 | box-shadow: inset 0 -1px 0 0 #151310; 542 | } 543 | } 544 | 545 | & h2:nth-of-type(2) { 546 | margin-block-start: 3em; 547 | } 548 | 549 | & :matches(h1, h2, h3, h4, h5, h6) a { 550 | color: inherit; 551 | position: relative; 552 | 553 | &::before { 554 | content: "#"; 555 | display: inline-block; 556 | inset-inline-start: -.75em; 557 | opacity: 0; 558 | position: absolute; 559 | transition: opacity 500ms; 560 | } 561 | 562 | &:hover::before { 563 | opacity: 1; 564 | } 565 | } 566 | 567 | & h3 { 568 | font-size: 1.125em; 569 | line-height: 1.2223; 570 | 571 | &:first-of-type { 572 | margin-block-start: 3em; 573 | } 574 | } 575 | 576 | & p a { 577 | background-image: linear-gradient(currentColor 25%, transparent 50%); 578 | background-position: 200% 100%; 579 | background-repeat: no-repeat; 580 | background-size: 200% 2px; 581 | cursor: pointer; 582 | padding-block-end: 2px; 583 | transition: background-position 250ms; 584 | 585 | &:hover { 586 | background-position: 100% 100%; 587 | } 588 | } 589 | 590 | & blockquote { 591 | border-left: .25em solid #dfe2e5; 592 | color: #6a737d; 593 | letter-spacing: .05em; 594 | padding-inline: 1em; 595 | 596 | @media (prefers-color-scheme: dark) { 597 | border-left-color: #201d1a; 598 | color: #d3d7de; 599 | } 600 | } 601 | 602 | & hr { 603 | background: #e1e4e8; 604 | border: none; 605 | height: .25em; 606 | margin: 24px 0; 607 | overflow: hidden; 608 | padding: 0; 609 | 610 | @media (prefers-color-scheme: dark) { 611 | background: #1e1b17; 612 | } 613 | } 614 | 615 | & img[align="left"] { 616 | margin-inline-end: 20px; 617 | } 618 | } 619 | 620 | .cssdb-deploys { 621 | text-align: center; 622 | } 623 | 624 | .cssdb-deploys__logo { 625 | display: inline-flex; 626 | } 627 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changes to cssdb 2 | 3 | ### Unreleased 4 | 5 | - Updated `@mdn/browser-compat-data` to `7.2.1` 6 | 7 | ### 8.5.2 (December 4, 2025) 8 | 9 | - Update `system-ui-font-family` polyfill url 10 | 11 | ### 8.5.1 (December 3, 2025) 12 | 13 | - Rename `position-area` to `position-area-property` 14 | 15 | ### 8.5.0 (December 3, 2025) 16 | 17 | - Added `position-area` 18 | 19 | ### 8.4.3 (December 1, 2025) 20 | 21 | - Updated `@mdn/browser-compat-data` to `7.1.23` 22 | 23 | ### 8.4.2 (September 21, 2025) 24 | 25 | - Add polyfill for `contrast-color-function` 26 | 27 | ### 8.4.1 (September 20, 2025) 28 | 29 | - Updated `@mdn/browser-compat-data` to `7.1.7` 30 | 31 | ### 8.4.0 (August 20, 2025) 32 | 33 | - Added `color-function-display-p3-linear` 34 | - Added `alpha-function` 35 | - Updated `@mdn/browser-compat-data` to `6.1.4` 36 | 37 | ### 8.3.1 (June 23, 2025) 38 | 39 | - Updated `@mdn/browser-compat-data` to `6.0.25` 40 | 41 | ### 8.3.0 (May 27, 2025) 42 | 43 | - Added `color-mix-variadic-function-arguments` 44 | 45 | ### 8.2.6 (May 26, 2025) 46 | 47 | - Updated `@mdn/browser-compat-data` to `6.0.17` 48 | 49 | ### 8.2.5 (April 15, 2025) 50 | 51 | - Updated `@mdn/browser-compat-data` to `6.0.5` 52 | 53 | ### 8.2.4 (March 10, 2025) 54 | 55 | - Updated `@mdn/browser-compat-data` to `5.7.1` 56 | 57 | ### 8.2.3 (December 9, 2024) 58 | 59 | - Updated `@mdn/browser-compat-data` to `5.6.22` 60 | 61 | ### 8.2.2 (December 2, 2024) 62 | 63 | - Updated `@mdn/browser-compat-data` to `5.6.20` 64 | 65 | ### 8.2.1 (November 11, 2024) 66 | 67 | - Added `sign-functions` plugin link 68 | 69 | ### 8.2.0 (November 11, 2024) 70 | 71 | - Added `random-function` 72 | 73 | ### 8.1.2 (October 14, 2024) 74 | 75 | - Updated `@mdn/browser-compat-data` to `5.6.6` 76 | 77 | ### 8.1.1 (September 9, 2024) 78 | 79 | - Updated `@mdn/browser-compat-data` to `5.5.51` 80 | 81 | ### 8.1.0 (July 7, 2024) 82 | 83 | - Added `content-alt-text` 84 | - Updated `@mdn/browser-compat-data` to `5.5.37` 85 | 86 | ### 8.0.2 (May 23, 2024) 87 | 88 | - Updated `@mdn/browser-compat-data` to `5.5.29` 89 | 90 | ### 8.0.1 (May 10, 2024) 91 | 92 | - Updated `@mdn/browser-compat-data` to `5.5.26` 93 | 94 | ### 8.0.0 (March 30, 2024) 95 | 96 | - Added `contrast-color-function` 97 | - Added `web-feature` identifiers for some features 98 | - Changed [license to `MIT-0` to align with `postcss-preset-env`](https://preset-env.cssdb.org/blog/license-change/) 99 | - Removed `color-contrast` 100 | - Removed `color-mod-function` 101 | - Removed `custom-property-sets` 102 | - Removed `gray-function` 103 | - Removed `matches-pseudo-class` 104 | 105 | ### 7.11.2 (March 9, 2024) 106 | 107 | - Updated `@mdn/browser-compat-data` to `5.5.14` (patch) 108 | - Take `mixed_type_parameters` into account for most color features. 109 | 110 | ### 7.11.1 (February 26, 2024) 111 | 112 | - Updated `@mdn/browser-compat-data` to `5.5.11` (patch) 113 | - Updated `caniuse-lite` to `1.0.30001589` (patch) 114 | 115 | ### 7.11.0 (February 17, 2024) 116 | 117 | - Added: Stage 2 `light-dark-function` 118 | 119 | ### 7.10.0 (December 27, 2023) 120 | 121 | - Added `interoperable_at` to make it possible to calculate the Baseline status of a feature 122 | - Updated `@mdn/browser-compat-data` to `5.5.2` (patch) 123 | - Updated `caniuse-lite` to `1.0.30001571` (patch) 124 | 125 | ### 7.9.1 (December 11, 2023) 126 | 127 | - Updated `@mdn/browser-compat-data` to `5.4.5` (patch) 128 | - Updated `caniuse-lite` to `1.0.30001568` (patch) 129 | 130 | ### 7.9.0 (October 31, 2023) 131 | 132 | - Added: Stage 2 `logical-overflow` 133 | - Added: Stage 2 `logical-overscroll-behavior` 134 | - Updated `@mdn/browser-compat-data` to `5.3.25` (patch) 135 | - Updated `caniuse-lite` to `1.0.30001553` (patch) 136 | 137 | ### 7.8.0 (October 08, 2023) 138 | 139 | - Added: Stage 2 `gamut-mapping` 140 | - Updated `@mdn/browser-compat-data` to `5.3.21` (patch) 141 | 142 | ### 7.7.3 (October 05, 2023) 143 | 144 | - Updated `@mdn/browser-compat-data` to `5.3.20` (patch) 145 | - Updated `caniuse-lite` to `1.0.30001546` (patch) 146 | 147 | ### 7.7.2 (September 4, 2023) 148 | 149 | - Updated `@mdn/browser-compat-data` to `5.3.14` (patch) 150 | - Updated `caniuse-lite` to `1.0.30001525` (patch) 151 | 152 | ### 7.7.1 (August 20, 2023) 153 | 154 | - Updated `@mdn/browser-compat-data` to `5.3.11` (patch) 155 | - Updated `caniuse-lite` to `1.0.30001522` (patch) 156 | 157 | ### 7.7.0 (July 24, 2023) 158 | 159 | - Added: Stage 2 `src-function` 160 | - Updated `@mdn/browser-compat-data` to `5.3.5` (patch) 161 | - Updated `caniuse-lite` to `1.0.30001517` (patch) 162 | 163 | ### 7.6.0 (May 15, 2023) 164 | 165 | - Added: Stage 2 `relative-color-syntax` 166 | - Updated `@mdn/browser-compat-data` to `5.2.57` (patch) 167 | - Updated `caniuse-lite` to `1.0.30001487` (patch) 168 | - `color-contrast` is now Stage 1 169 | - `container-queries` is now Stage 2 170 | - `font-format-keywords` is now Stage 2 171 | - `nesting-rules` is now Stage 2 172 | - `overscroll-behavior-property` is now Stage 2 173 | 174 | ### 7.5.4 (April 10, 2023) 175 | 176 | - Updated `@mdn/browser-compat-data` to `5.2.49` (patch) 177 | - Updated `caniuse-lite` to `1.0.30001477` (patch) 178 | 179 | ### 7.5.3 (March 31, 2023) 180 | 181 | - Updated support data for `media-query-ranges` 182 | 183 | ### 7.5.2 (March 28, 2023) 184 | 185 | - Updated support data for `color-mix` 186 | - Updated support data for `gradients-interpolation-method` 187 | 188 | ### 7.5.1 (March 24, 2023) 189 | 190 | - Fixed potentially old generated files (patch) 191 | 192 | ### 7.5.0 (March 24, 2023) 193 | 194 | - Added: Stage 2 `gradients-interpolation-method` 195 | - Updated `@mdn/browser-compat-data` to `5.2.45` (patch) 196 | - Updated `caniuse-lite` to `1.0.30001469` (patch) 197 | 198 | ### 7.4.1 (January 24, 2023) 199 | 200 | - Added: Links to plugins. 201 | 202 | ### 7.4.0 (January 22, 2023) 203 | 204 | - Added: Stage 2 `logical-resize` 205 | 206 | ### 7.3.0 (January 19, 2023) 207 | 208 | - Added: Stage 2 `logical-viewport-units` 209 | - Updated `@mdn/browser-compat-data` to `5.2.29` (patch) 210 | - Updated `caniuse-lite` to `1.0.30001445` (patch) 211 | 212 | ### 7.2.1 (January 9, 2023) 213 | 214 | - Updated `@mdn/browser-compat-data` to `5.2.26` (patch) 215 | - Updated `caniuse-lite` to `1.0.30001442` (patch) 216 | - Updated `postcss` to `8.4.21` (patch) 217 | 218 | ### 7.2.0 (November 29, 2022) 219 | 220 | - Added: Stage 2 `media-queries-aspect-ratio-number-values` 221 | - Updated `@mdn/browser-compat-data` to `5.2.19` (patch) 222 | - Updated `caniuse-lite` to `1.0.30001434` (patch) 223 | 224 | ### 7.1.0 (November 4, 2022) 225 | 226 | - Added: Stage 2 `scope-pseudo-class` 227 | - Updated `@mdn/browser-compat-data` to `5.2.12` (patch) 228 | - Updated `caniuse-lite` to `1.0.30001430` (patch) 229 | - Updated `postcss` to `8.4.18` (patch) 230 | - Updated `postcss-preset-env` to `7.8.2` (patch) 231 | - Updated `stylelint` to `14.14.1` (patch) 232 | 233 | ### 7.0.2 (October 14, 2022) 234 | 235 | - Updated `@mdn/browser-compat-data` to `5.2.6` (patch) 236 | - Updated `browserslist` to `4.21.4` (patch) 237 | - Updated `caniuse-lite` to `1.0.30001418` (patch) 238 | 239 | ### 7.0.1 (August 23, 2022) 240 | 241 | - Updated `@mdn/browser-compat-data` to `5.1.8` (patch) 242 | - Updated `caniuse-lite` to `1.0.30001382` (patch) 243 | 244 | ### 7.0.0 (August 15, 2022) 245 | 246 | - Updated most features to use data from [@mdn/browser-compat-data](https://github.com/mdn/browser-compat-data) 247 | - Updated `environment-variables` to mean **custom** environment variables. No browsers currently support this feature. (breaking) 248 | - Added: Stage 2 `nested-calc`. 249 | - Added: Stage 2 `text-decoration-shorthand`. 250 | - Added: Stage 2 `float-clear-logical-values`. 251 | - Updated `@mdn/browser-compat-data` to `5.1.8` (minor) 252 | - Updated `browserslist` to `4.21.3` (minor) 253 | - Updated `caniuse-lite` to `1.0.30001376` (patch) 254 | 255 | ### 6.6.3 (June 3, 2022) 256 | 257 | - Updated `case-insensitive-attributes` plugin's url 258 | - Updated `custom-media-queries` plugin's url 259 | - Updated `custom-selectors` plugin's url 260 | - Updated `not-pseudo-class` plugin's url 261 | - Included `trigonometric-functions` plugin as bundled by PostCSS Preset Env 262 | - Updated `@mdn/browser-compat-data` to `5.0.1` (major) 263 | - Updated `caniuse-lite` to `1.0.30001346` (patch) 264 | - Updated `postcss-preset-env` to `7.7.0` (minor) 265 | 266 | ### 6.6.2 (May 23, 2022) 267 | 268 | - Added polyfills to `cascade-layer` and `trigonometric-functions`. 269 | 270 | ### 6.6.1 (May 2, 2022) 271 | 272 | - Added polyfills to `unset-value` and `stepped-value-functions` (patch) 273 | 274 | ### 6.6.0 (May 1, 2022) 275 | 276 | - Added: Stage 2 Exponential (`pow()`, `sqrt()`, `hypot()`, `log()`, `exp()`) functions (minor). 277 | - Added: Stage 2 `calc()` constants (`e`, `pi`, `infinity`, `-infinity` and `NaN`) (minor). 278 | - Added: Stage 2 Sign (`abs()` and `sign()`) functions (minor). 279 | - Added: Stage 2 Trigonometric (`sin()`, `cos()`, `tan()`, `asin()`, `acos()`, `atan()` and `atan2()`) functions (minor). 280 | - Updated `@mdn/browser-compat-data` to `4.1.18` (patch) 281 | - Updated `caniuse-lite` to `1.0.30001334` (patch) 282 | 283 | ### 6.5.0 (March 15, 2022) 284 | 285 | - Updated `@mdn/browser-compat-data` to `4.1.11` (patch) 286 | - Updated `astro` to `0.24.0` (minor) 287 | - Updated `caniuse-lite` to `1.0.30001317` (patch) 288 | - Fixed issue in which features under a flag on MDN were counting as implemented. 289 | - Added: Stage 2 cascade layers. (minor) 290 | - `color-mix` is now Stage 2 (was deprecated) and has links to an official spec! 🎉 (minor) 291 | 292 | ### 6.4.1 (March 7, 2022) 293 | 294 | - Updated `@mdn/browser-compat-data` to `4.1.10` (patch). This now shows correct support for `:where` on Safari version 14. 295 | - Updated `astro` to `0.23.7` (patch) 296 | - Updated `browserslist` to `4.20.0` (minor) 297 | - Updated `caniuse-lite` to `1.0.30001313` (patch) 298 | - Updated `postcss` to `8.4.8` (patch) 299 | - Updated `postcss-preset-env` to `7.4.2` (patch) 300 | - Updated `stylelint` to `14.5.3` (patch) 301 | 302 | ### 6.4.0 (February 19, 2022) 303 | 304 | - Added: `unset-value` function feature as Stage 3. 305 | - Updated `@astropub/webapi` to`0.10.14` (patch) 306 | - Updated `@mdn/browser-compat-data` to `4.1.8` (patch) 307 | - Updated `astro` to `0.23.0` (minor) 308 | - Updated `browserslist` to `4.19.3` (patch) 309 | - Updated `postcss-preset-env` to `7.4.1` (minor) 310 | - Updated `stylelint` to `14.5.1` (minor) 311 | 312 | ### 6.3.1 (February 16, 2022) 313 | 314 | - Fix certain examples 315 | - Adding new features to PostCSS Preset Env bundled collection. 316 | - Ensuring all features that have plugins have them listed. 317 | 318 | ### 6.3.0 (February 12, 2022) 319 | 320 | - Added: Stage 2 `oklab` function (minor). 321 | - Added missing polyfills for `clamp()`, `opacity` percentages, `:is` pseudo and `display-two-values`. 322 | - Fixed `and_chr` and `android` browsers always using the latest version on Can I Use, now leverages Desktop version if the supported version is the latest one. See [Fyrd/caniuse#3518](https://github.com/Fyrd/caniuse/issues/3518). 323 | - Updated `stylelint` to `14.5.0` (minor) 324 | - Updated `caniuse-lite` to `1.0.30001311` (patch) 325 | - Updated `postcss-preset-env` to `7.3.2` (patch) 326 | 327 | ### 6.2.1 (February 10, 2022) 328 | 329 | - Returning `example` to the exported DB (removed on `6.0.0`). 330 | - Removed outdated plugin from Container Queries. 331 | - Added link to experimental version of `:has`. 332 | - Updated `@astropub/webapi` to `0.10.13` (patch) 333 | - Updated `@mdn/browser-compat-data` to `4.1.7` (patch) 334 | - Updated `caniuse-lite` to `1.0.30001310` (patch) 335 | - Updated `stylelint` to `14.4.0` (minor) 336 | - Updated `stylelint-config-standard` to`25.0.0` (major) 337 | 338 | ### 6.2.0 (February 5, 2022) 339 | 340 | - Added: `color()` function feature as Stage 2. 341 | - Fixed `vendors_implementations` so it doesn't count unreleased browsers. 342 | - Updated `postcss-preset-env` to `^7.3.1` (minor) 343 | - Updated `@mdn/browser-compat-data` to `^4.1.6` (patch) 344 | - Updated `caniuse-lite` to `^1.0.30001307` (patch) 345 | - Updated `postcss` to `^8.4.6` (patch) 346 | 347 | ### 6.1.0 (January 31, 2022) 348 | 349 | - `blank-pseudo-class` is now Stage 2 and has links to an official spec! 🎉 (minor) 350 | - `clamp` is now Stage 2 and has links to an official spec! 🎉 (minor) 351 | - `color-contrast` is now Stage 2 and has links to an official spec! 🎉 (minor) 352 | - `color-functional-notation` is now Stage 2 and has links to an official spec! 🎉 (minor) 353 | - `custom-media-queries` is now Stage 2 and has links to an official spec! 🎉 (minor) 354 | - `display-two-values` is now Stage 2 and has links to an official spec! 🎉 (minor) 355 | - `opacity-percentage` is now Stage 2 and has links to an official spec! 🎉 (minor) 356 | - `prefers-color-scheme-query` is now Stage 2 and has links to an official spec! 🎉 (minor) 357 | - `prefers-reduced-motion-query` is now Stage 2 and has links to an official spec! 🎉 (minor) 358 | - `where-pseudo-class` is now Stage 2 and has links to an official spec! 🎉 (minor) 359 | - `container-queries` has official Working Draft linked (instead of Editor's Draft). 360 | - `nesting-rules` has official Working Draft linked (instead of Editor's Draft). 361 | - `overscroll-behavior-property` has official Working Draft linked (instead of Editor's Draft). 362 | - Updated `@mdn/browser-compat-data` to `4.1.5` (patch) 363 | - Updated `@astropub/webapi` to `0.10.11` (patch) 364 | - Updated `astro` to `0.22.20` (patch) 365 | - Updated `caniuse-lite` to `1.0.30001304` (patch) 366 | - Updated `stylelint` to `14.3.0` (minor) 367 | 368 | ### 6.0.2 (January 21, 2022) 369 | 370 | - Updated mechanism to calculate browser versions to be more reliable. This also introduces the option through `allow_partial_implementation` if we want to allow something such as Autoprefixer to take care with prefixing a given feature such as `:any-link`. 371 | - Ensuring pre-releases aren't counted as vendor implementation. This was flagging that `:has` had 1 vendor implementation, but it's not in stable Safari yet. 372 | - Removed `media-query-ranges` support data as it was not correct that Firefox supports it fully. See [#57](https://github.com/csstools/cssdb/issues/57) and [mdn/browser-compat-data#14593](https://github.com/mdn/browser-compat-data/issues/14593) 373 | - Updated `@mdn/browser-compat-data` to `4.1.4` (patch) 374 | - Updated `astro` to `0.22.16` (patch) 375 | - Updated `caniuse-lite` to `1.0.30001300` (patch) 376 | 377 | ### 6.0.1 (January 7, 2022) 378 | 379 | - Updated: Conditional media queries now Stage 2 and has links to an official spec! 🎉 (minor) 380 | - Updated `astro` to `0.22.8` (patch). 381 | - Updated `caniuse-lite` to `1.0.30001297` (patch). 382 | 383 | ### 6.0.0 (January 7, 2022) 384 | 385 | - Exported CSSDB no longer exposes `example`, `caniuse` nor `caniuse-compat` (breaking). 386 | - Added `browser_support` field that contains a dictionary with the earliest supported version. 387 | - Added a mechanism to fetch browser support from [mdn](https://github.com/mdn/browser-compat-data). 388 | - Added `vendors_implementations` that states how many vendors have implemented the feature. 389 | - Added: Stage 1 `clamp()` function (minor) 390 | - Added: Stage 1 Two values syntax for `display` (minor) 391 | - Added: Stage 1 percentages for opacity (minor) 392 | - Updated `postcss-preset-env` to `7.2.0` (minor). 393 | - Updated `@astropub/webapi` to `0.10.2` (minor). 394 | - Updated `astro` to `0.22.7` (patch). 395 | - Updated `caniuse-lite` to `1.0.30001296` (patch). 396 | 397 | ### 5.1.0 (January 4, 2022) 398 | 399 | - Updated: All plugins urls are pointing to the right place. 400 | - Added: Exported now a cssdb.mjs to allow support of ESM modules. 401 | - Added: `color-contrast()` function. 402 | - Added: Container Queries. 403 | - Updated: `env()` now has Browser Support table. 404 | - Updated: `nesting-rules` now have Browser Support table and Can I Use property. 405 | - Updated: `prefers-color-scheme` Media Query Browser Support. 406 | - Updated: Font `format()` Keywords now has MDN docs. 407 | - Updated: `hwb()` Function now has MDN docs. 408 | - Updated: `ic` unit now has MDN docs. 409 | - Updated: `lch()` Function now has MDN docs. 410 | - Updated: `:matches()` Pseudo Class now has MDN docs. 411 | - Updated: `:where()` Zero-Specificity Pseudo-Class now has MDN docs. 412 | - Updated: `all` Property is now Stage 3! 🎉 413 | - Updated: `gray()` function is now Stage -1! 🙁 414 | 415 | ### 5.0.0 (May 24, 2019) 416 | 417 | - Removed: Rejected `:matches()` psuedo-class (major) 418 | - Added: Stage 2 `:is()` pseudo-class (minor) 419 | - Added: MDN docs for `image-set-function` (patch) 420 | - Added: MDN docs for `:blank` pseudo-class (patch) 421 | 422 | ### 4.4.0 (March 7, 2019) 423 | 424 | - Updated: Nesting Rules are now Stage 1! 🎉 425 | 426 | ### 4.3.0 (December 12, 2018) 427 | 428 | - Added: `:blank` Empty-Value Pseudo-Class 429 | - Added: caniuse link for `:has()` Relational Pseudo-Class 430 | - Added: JavaScript Library and PostCSS Plugin links for the `:blank` 431 | Empty-Value Pseudo-Class and the `:has()` Relational Pseudo-Class 432 | 433 | ### 4.2.0 (November 4, 2018) 434 | 435 | - Added: Documentation links to MDN 436 | - Added: `color-adjust` Property 437 | - Added: `overscroll-behavior` Property 438 | - Added: `prefers-color-scheme` Media Query 439 | - Added: `prefers-reduced-motion` Media Query 440 | - Added: `:in-range` and `:out-of-range` Pseudo-Classes 441 | - Added: `:read-only` and `:read-write` selectors 442 | 443 | This also updates the cssdb.org template and styles, using postcss-preset-env 444 | to create and minifying cross-browser compatible css, improving font loading 445 | and display, supporting RTL displays, and adding MDN documentation. 446 | 447 | ### 4.1.1 (October 28, 2018) 448 | 449 | - Added: caniuse link for `overflow` shorthand property 450 | 451 | ### 4.1.0 (October 28, 2018) 452 | 453 | - Added: Double Position Gradients 454 | 455 | ### 4.0.0 (October 23, 2018) 456 | 457 | - Changed: `:something()` is now `:where()` and moved from Stage 2 to Stage 1 458 | 459 | ### 3.2.1 (August 31st, 2018) 460 | 461 | - Updated: caniuse-like browser statistics for Custom Environment Variables 462 | 463 | I really wish caniuse would start adding some of these. 464 | 465 | ### 3.2.0 (August 30th, 2018) 466 | 467 | - Removed: Rejected `color-mod()` function 468 | - Updated: Overflow shorthand is now Stage 2 469 | - Updated: caniuse-like browser statistics for Custom Environment Variables and 470 | Overflow Shorthand 471 | 472 | ### 3.1.0 (May 11th, 2018) 473 | 474 | - Added: Polyfills for `lab-function` and `lch-function` 475 | 476 | ### 3.0.0 (May 10th, 2018) 477 | 478 | - Changed: All stages from 1-4 to 0-4 to align with TC39 479 | - Updated: Tests, badges, descriptions, and dependencies 480 | 481 | ### 2.2.0 (May 7th, 2018) 482 | 483 | - Added: Place Properties as Stage 2 484 | - Added: PostCSS plugin for Color Functional Notation 485 | - Updated: Media Query Ranges to Stage 4 486 | 487 | ### 2.1.0 (May 1st, 2018) 488 | 489 | - Added: Environment Variables as Stage 1 490 | - Added: `overflow` Property as Stage 2 491 | - Added: Gap Properties as Stage 4 492 | 493 | ### 2.0.0 (April 7th, 2018) 494 | 495 | - Renamed: GitHub repository from `css-db` to `cssdb`, now aligning with npm 496 | - Renamed: All feature IDs. 497 | - Updated: Documentation. 498 | 499 | Notes: The old feature IDs were problematic because they attempted to follow 500 | specification section IDs, but some specifications weren’t aren’t always 501 | covered by a single section, and many sections were inconsistently named. 502 | Because there was no pattern one could predict for any of the headings, a new 503 | system was created; to **name** the feature and provide **context**. This meant 504 | a feature ID like `css-cascade-all-shorthand` became `all-property`, and 505 | `css-fonts-propdef-font-variant` became `font-variant-property`, etc. This 506 | greatly simplified all of the feature IDs and allowed for more predictive 507 | naming moving forward. 508 | 509 | ### 1.6.0 (February 18th, 2018) 510 | 511 | - Added: Break Properties 512 | 513 | ### 1.5.2 (February 18th, 2018) 514 | 515 | - Updated: `:focus-within` polyfills 516 | 517 | ### 1.5.1 (February 17th, 2018) 518 | 519 | - Fixed: `:focus-visible` and `:focus-within` title syntax 520 | 521 | ### 1.5.0 (January 22th, 2018) 522 | 523 | - Changed: Use the latest published specification URL whenever possible 524 | - Changed: Upgrade Color #RRGGBBAA Notation to Stage 3 525 | - Changed: Upgrade Color gray() Function to Stage 3 526 | - Changed: Upgrade Color color-mod() Function to Stage 3 527 | - Changed: Upgrade Color hwb() Function to Stage 3 528 | - Changed: Downgrade Custom Properties to Stage 4 529 | - Fixed: Color hwb() Function example 530 | - Fixed: the Color rebeccapurple PostCSS Plugin URL 531 | 532 | ### 1.4.0 (January 16th, 2018) 533 | 534 | - Changed: polyfill for `css-color-modifying-colors` 535 | 536 | ### 1.3.0 (January 8th, 2018) 537 | 538 | - Added: caniuse references for `css-logical` and `css-fonts-system-ui-def` 539 | - Fixed: caniuse parsing for browser support 540 | 541 | ### 1.2.0 (January 8th, 2018) 542 | 543 | - Fixed: specification identifiers for `css-color-hwb-notation`, 544 | `selectors-dir-pseudo` 545 | - Fixed: Examples for Media Queries Custom Media Queries 546 | 547 | ### 1.1.0 (September 27th, 2017) 548 | 549 | - Added: Image `image-set()` Function, Selector `:dir` Pseudo-Class, 550 | Selector `:any-link` Pseudo-Class, Text `overflow-wrap` Property, 551 | Font `system-ui` Family, Cascade `all` Property 552 | - Added: caniuse identifiers 553 | - Fixed: Examples for Nesting, Media Queries Ranges 554 | 555 | ### 1.0.0 (September 6th, 2017) 556 | 557 | - Initial version 558 | --------------------------------------------------------------------------------