This file provides guidance for AI coding agents working on the sphinx-design repository.
sphinx-design is a Sphinx extension for designing beautiful, view size responsive web components. It provides:
- Grids, cards, dropdowns, tabs, badges, buttons, and icons
- Responsive design inspired by Bootstrap (v5), Material Design, and Material-UI
- Support for both reStructuredText and MyST Markdown (via integration with myst-parser)
The extension works with multiple Sphinx themes including alabaster, sphinx-rtd-theme, pydata-sphinx-theme, sphinx-book-theme, furo, and sphinx-immaterial.
pyproject.toml # Project configuration and dependencies
tox.ini # Tox test environment configuration
sphinx_design/ # Main source code
├── __init__.py # Package init with setup() entry point
├── _compat.py # Compatibility utilities
├── extension.py # Main Sphinx extension setup
├── shared.py # Shared constants and base classes
├── badges_buttons.py # Badge and button directives
├── cards.py # Card directives
├── dropdown.py # Dropdown directive
├── grids.py # Grid directives
├── tabs.py # Tab directives
├── icons.py # Icon roles (Material, FontAwesome, Octicons)
├── article_info.py # Article info directive
├── compiled/ # Bundled icon data (JSON)
└── static/ # Served static assets (compiled CSS, JS)
style/ # CSS sources (compiled by tools/generate_css.py)
├── design.toml # Declarative tokens for the generated utility families
├── cards.css # Hand-authored card styles
├── tabs.css # Hand-authored tab styles
├── dropdown.css # Hand-authored dropdown styles
└── ... # Other hand-authored component styles
tools/ # Dev-only tooling (not shipped in the wheel)
├── generate_css.py # Builds sphinx_design/static/sphinx-design.min.css
└── check_css_equivalence.py # CSS rule-set equivalence verifier (tinycss2)
tests/ # Test suite
├── conftest.py # Pytest fixtures
├── test_snippets.py # Snippet-based tests
├── test_misc.py # Miscellaneous tests
├── test_snippets/ # Test fixture files for snippets
└── test_misc/ # Test fixture files for misc tests
docs/ # Documentation source (MyST Markdown)
├── conf.py # Sphinx configuration
├── index.md # Documentation index
├── get_started.md # Getting started guide
├── grids.md # Grid documentation
├── cards.md # Card documentation
├── tabs.md # Tab documentation
├── dropdowns.md # Dropdown documentation
├── badges_buttons.md # Badge and button documentation
└── snippets/ # Code snippets for docs (myst/, rst/)
All commands should be run via tox for consistency. The project uses tox-uv for faster environment creation.
# Run all tests (default Python version)
tox
# Run tests with a specific Python version
tox -e py311
tox -e py312
# Run a specific test file
tox -- tests/test_snippets.py
# Run a specific test function
tox -- tests/test_snippets.py::test_function_name
# Run tests without myst-parser dependency
tox -e py311-no-myst
# Run with coverage
tox -- --cov=sphinx_design
# Update regression test fixtures
tox -- --force-regen# Build docs with different themes
tox -e docs-alabaster
tox -e docs-rtd
tox -e docs-pydata
tox -e docs-sbt
tox -e docs-furo
tox -e docs-im
# Clean build (set CLEAN env var)
CLEAN=1 tox -e docs-furo
# Build with a different builder (e.g., linkcheck)
BUILDER=linkcheck tox -e docs-furo# Type checking with mypy
tox -e mypy
# Linting with ruff (auto-fix enabled)
tox -e ruff-check -- --fix
# Formatting with ruff
tox -e ruff-fmt
# Run pre-commit hooks on all files
pre-commit run --all-files
# Regenerate the compiled CSS artifact
python tools/generate_css.py
# or via pre-commit
pre-commit run --all-files css- Formatter/Linter: Ruff (configured in
pyproject.toml) - Type Checking: Mypy (configured in
pyproject.toml) - Pre-commit: Use pre-commit hooks for consistent code style
- Type annotations: Use complete type annotations for all function signatures.
- Docstrings: Use Sphinx-style docstrings (
:param:,:return:,:raises:). - Directive classes: Extend
SdDirective(fromshared.py) for new directives. - Warning messages: Use
WARNING_TYPE = "design"for consistent warning categorization. - Testing: Write tests for all new functionality. Use
pytest-regressionsfor output comparison.
def create_component(
name: str,
rawtext: str,
*,
classes: Sequence[str] = (),
) -> nodes.container:
"""Create a component container node.
:param name: The component name.
:param rawtext: The raw text content.
:param classes: Additional CSS classes to apply.
:return: A container node with the component.
"""
...- Tests use
pytestwith fixtures fromconftest.py - Regression testing uses
pytest-regressionsfor output comparison - The
sphinx_builderfixture creates temporary Sphinx projects for testing
- Use the
sphinx_builderfixture to create test Sphinx applications - Add test content as
.txtfiles intests/test_snippets/ortests/test_misc/ - Use
file_regressionordata_regressionfixtures for comparing output
def test_grid_basic(sphinx_builder, file_regression):
builder = sphinx_builder()
builder.src_path.joinpath("index.md").write_text(
"""
# Test
````{grid} 2
:gutter: 1
```{grid-item}
Item 1
```
```{grid-item}
Item 2
```
````
""",
encoding="utf8",
)
builder.build()
doctree = builder.get_doctree("index")
file_regression.check(doctree.pformat(), extension=".xml")Use this format:
<EMOJI> <KEYWORD>: Summarize in 72 chars or less (#<PR>)
Optional detailed explanation.
Keywords:
✨ NEW:– New feature🐛 FIX:– Bug fix👌 IMPROVE:– Improvement (no breaking changes)‼️ BREAKING:– Breaking change📚 DOCS:– Documentation🔧 MAINTAIN:– Maintenance changes only (typos, etc.)🧪 TEST:– Tests or CI changes only♻️ REFACTOR:– Refactoring
If the commit only makes changes to a single module, consider including the name in the title.
Use the same as for the commit message format,
but for the title you can omit the KEYWORD and only use EMOJI
When submitting changes:
- Description: Include a meaningful description or link explaining the change
- Tests: Include test cases for new functionality or bug fixes
- Documentation: Update docs if behavior changes or new features are added
- Changelog: Update
CHANGELOG.mdunder the appropriate section - Code Quality: Ensure
pre-commit run --all-filespasses
The extension follows a modular architecture where each component type has its own module:
setup() in __init__.py
└── setup_extension() in extension.py
├── setup_badges_and_buttons()
├── setup_cards()
├── setup_grids()
├── setup_dropdown()
├── setup_icons()
├── setup_tabs()
└── setup_article_info()
SdDirective: Base class for sphinx-design directives (extendsSphinxDirective)create_component(): Factory function for creating component nodesWARNING_TYPE: Constant for warning categorization
Each component type has its own module with directives:
- Grids (
grids.py):grid,grid-item,grid-item-carddirectives - Cards (
cards.py):card,card-header,card-footer,card-carouseldirectives - Tabs (
tabs.py):tab-set,tab-itemdirectives - Dropdowns (
dropdown.py):dropdowndirective - Badges/Buttons (
badges_buttons.py):button-link,button-refdirectives andbdg-*roles - Icons (
icons.py): Icon roles for Material, FontAwesome, and Octicons
Compiled CSS and JS are stored in sphinx_design/static/:
- CSS is generated from the
style/sources (see below) - JavaScript for tab functionality
The extension uses plain CSS, built by a small Python generator (no Node/Sass):
- Hand-authored component CSS lives in
style/*.css, expanded selectors only (no native CSS nesting), so the shipped stylesheet stays widely compatible. - The mechanical utility families (spacing, sizing, borders, grid
columns/breakpoints, semantic colours, ...) are data-driven: edit
style/design.tomlrather than hand-writing repetitive rules. python tools/generate_css.pyconcatenates the hand-authored CSS with the generated utilities, minifies the result, and writes the single artifactsphinx_design/static/sphinx-design.min.css(the served filename is public API — do not rename it). The pre-commitcsshook runs this and fails if the committed artifact is stale.- CSS is automatically copied to build output during Sphinx builds.
Runtime hover/focus shades use color-mix() with a static fallback, so a user
overriding a --sd-color-* custom property gets a matching hover shade. New CSS
should follow the Baseline Widely Available support
policy; a Baseline Newly Available feature is acceptable only where it
degrades gracefully (document the exception).
pyproject.toml- Project configuration, dependencies, and tool settingssphinx_design/__init__.py- Package entry point withsetup()for Sphinxsphinx_design/extension.py- Main extension setup, CSS/JS handlingsphinx_design/shared.py- Base classes and shared utilitiessphinx_design/grids.py- Grid layout directivessphinx_design/cards.py- Card component directivessphinx_design/tabs.py- Tab component directives
- Build docs with
-vflag for verbose output - Check
docs/_build/html/<theme>/for build outputs - Use
tox -- -v --tb=longfor verbose test output with full tracebacks - Inspect generated HTML to debug styling issues
- Create a new module in
sphinx_design/(e.g.,my_component.py) - Define directive class(es) extending
SdDirective - Create a
setup_my_component(app: Sphinx)function - Call the setup function from
setup_extension()inextension.py - Add styles: hand-authored rules in
style/<my_component>.css, or (for repetitive/data-driven families) tokens instyle/design.toml - Wire the new file into the
ASSEMBLYorder intools/generate_css.pyand run it to regenerate the artifact - Document in
docs/ - Add tests in
tests/
- Add the option to the directive's
option_specdictionary - Handle the option in the directive's
run()method - Add appropriate CSS classes or attributes to the output node
- Document the new option
- Add tests for the new option
- Edit the sources in
style/(*.cssfor hand-authored rules,design.tomlfor the generated utility families) - Run
python tools/generate_css.pyto rebuild (orpre-commit run --all-files css) - Compiled output goes to
sphinx_design/static/sphinx-design.min.css - Test with different themes to ensure compatibility
The cascade is the ASSEMBLY order in tools/generate_css.py: hand files
and generated blocks are concatenated in that exact sequence, and
equal-specificity rules that set the same property resolve by source order
(last wins). Reordering an ASSEMBLY entry — or moving a rule between a hand
file and a generator — can therefore change rendering even though every rule
still exists. When touching the order, verify with
python tools/check_css_equivalence.py OLD.css NEW.css: its order pass flags
exactly these regressions (source-order inversions between co-applying,
equal-specificity rules that share a property). The generator also refuses to
build if ASSEMBLY, the generator table and the style/ directory drift out
of sync, or if design.toml fails its schema check.