Skip to main content

mdx_better_lists

PyPI Python Versions License CI codecov Code style: black

A Python-Markdown extension for better list handling, providing more intuitive list behavior and formatting with fine-grained control over list rendering. Created with Test-Driven Development (TDD) principles to ensure reliability and maintainability.

Features

  • Configurable nested indentation - Control how many spaces are required for nested lists (default: 2)
  • Marker-based list separation - Automatically separate lists when marker types change (-, *, +)
  • Blank line list separation - Separate unordered lists when blank lines appear between items
  • Loose list control - Control paragraph wrapping in ordered lists with blank lines
  • Number preservation - Optionally preserve exact list numbers from markdown source
  • Always start at one - Force ordered lists to always start at 1
  • Paragraph-list splitting - Optionally split paragraphs and lists without requiring blank lines between them

Installation

pip install mdx_better_lists

Usage

Basic Usage

from markdown import markdown

text = """
- Item 1
- Item 2

- Item 3
"""

html = markdown(text, extensions=['mdx_better_lists'])

With Configuration

from markdown import markdown

text = """
1. First
2. Second
2. Another second
"""

html = markdown(text, extensions=['mdx_better_lists'],
                extension_configs={'mdx_better_lists': {
                    'preserve_numbers': True
                }})

Configuration Options

Option Type Default Description
nested_indent int 2 Number of spaces required for nested list indentation
marker_separation bool True Separate lists when marker types (-, *, +) differ
unordered_list_separation bool True Separate unordered lists when blank lines appear between items
ordered_list_loose bool True Wrap ordered list items in <p> tags when blank lines separate them
preserve_numbers bool False Preserve exact list numbers from markdown (use value attribute)
always_start_at_one bool False Force all ordered lists to start at 1
split_paragraph_lists bool False Split paragraphs and lists when they appear without blank lines between them

Configuration Details

nested_indent (default: 2)

Controls how many spaces are required for a list item to be considered nested.

# With nested_indent=2 (default)
- Parent
  - Nested  # 2 spaces = nested

# With nested_indent=4
- Parent
    - Nested  # 4 spaces = nested
  - Not nested  # 2 spaces = not nested

marker_separation (default: True)

When enabled, lists with different markers (-, *, +) are separated into different <ul> elements.

# With marker_separation=True (default)
- Item with dash
+ Item with plus  # Creates a new <ul>

# Output: Two separate <ul> elements

# With marker_separation=False
- Item with dash
+ Item with plus  # Same <ul>

# Output: Single <ul> element

unordered_list_separation (default: True)

When enabled, unordered lists are separated into different <ul> elements when blank lines appear.

# With unordered_list_separation=True (default)
- First

- Second  # Creates a new <ul>

# Output: Two separate <ul> elements

# With unordered_list_separation=False
- First

- Second  # Same <ul>

# Output: Single <ul> element

ordered_list_loose (default: True)

When enabled, ordered list items are wrapped in <p> tags when blank lines separate them.

# With ordered_list_loose=True (default)
1. First

2. Second

# Output:
<ol>
  <li><p>First</p></li>
  <li><p>Second</p></li>
</ol>

# With ordered_list_loose=False
1. First

2. Second

# Output:
<ol>
  <li>First</li>
  <li>Second</li>
</ol>

preserve_numbers (default: False)

When enabled, preserves exact list numbers from the markdown source using the value attribute.

# With preserve_numbers=True
1. First
2. Second
2. Another second
3. Third

# Output:
<ol>
  <li value="1">First</li>
  <li value="2">Second</li>
  <li value="2">Another second</li>
  <li value="3">Third</li>
</ol>

always_start_at_one (default: False)

When enabled, forces all ordered lists to start at 1, ignoring the starting number in markdown.

# With always_start_at_one=True
5. Fifth
6. Sixth

# Output:
<ol>
  <li>Fifth</li>
  <li>Sixth</li>
</ol>
# (renders as 1, 2 instead of 5, 6)

# With always_start_at_one=False (default)
5. Fifth
6. Sixth

# Output:
<ol start="5">
  <li>Fifth</li>
  <li>Sixth</li>
</ol>

split_paragraph_lists (default: False)

When enabled, automatically splits paragraphs and lists that appear without blank lines between them into separate blocks. This allows lists to be recognized immediately after paragraphs without requiring a blank line separator.

# With split_paragraph_lists=False (default)
This is a paragraph before the list.
- First item
- Second item

# Output:
<p>This is a paragraph before the list.
- First item
- Second item</p>
# (list markers are treated as plain text)

# With split_paragraph_lists=True
This is a paragraph before the list.
- First item
- Second item

# Output:
<p>This is a paragraph before the list.</p>
<ul>
  <li>First item</li>
  <li>Second item</li>
</ul>
# (paragraph and list are separated)

This also works with ordered lists:

# With split_paragraph_lists=True
Introduction paragraph.
1. First point
2. Second point

# Output:
<p>Introduction paragraph.</p>
<ol>
  <li>First point</li>
  <li>Second point</li>
</ol>

Note: This feature only operates at the top level. List markers inside list items that are not properly indented will remain as text (standard Markdown behavior).

Examples

Example 1: Marker Separation

from markdown import markdown

text = """
- Item with dash
- Another dash

+ Item with plus
+ Another plus
"""

html = markdown(text, extensions=['mdx_better_lists'])

# Output: Three separate <ul> elements (blank line + marker change)

Example 2: Nested Lists with Custom Indentation

from markdown import markdown

text = """
- Parent
    - Nested (4 spaces)
        - Deeply nested (8 spaces)
"""

html = markdown(text, extensions=['mdx_better_lists'],
                extension_configs={'mdx_better_lists': {
                    'nested_indent': 4
                }})

Example 3: Preserving List Numbers

from markdown import markdown

text = """
1. Introduction
1. Background
1. Methods
"""

html = markdown(text, extensions=['mdx_better_lists'],
                extension_configs={'mdx_better_lists': {
                    'preserve_numbers': True
                }})

# Each item gets value="1"

Migration from mdx_truly_sane_lists

# mdx_truly_sane_lists
markdown(text, extensions=['mdx_truly_sane_lists'],
         extension_configs={'mdx_truly_sane_lists': {
             'truly_sane': True  # Default
             'nested_indent': 2  # Default
         }})

# Equivalent in mdx_better_lists (this is the default)
markdown(text, extensions=['mdx_better_lists'],
         extension_configs={'mdx_better_lists': {
             'marker_separation': True, # Default
             'unordered_list_separation': True, # Default
             'ordered_list_loose': True # Default
             'nested_indent': 2  # Default
         }})

Note: mdx_better_lists does not support loose list behavior (paragraph wrapping) for unordered lists. Unordered lists always remain tight, even when both marker_separation and unordered_list_separation are set to False.

Development

This project follows Test-Driven Development (TDD) principles.

Running Tests

pytest tests/

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

Metadata

Release files for mdx-better-lists 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mdx-better-lists 1.1.0
File Size Uploaded
mdx_better_lists-1.1.0.tar.gz 23.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mdx-better-lists 1.1.0
File Interpreter ABI Platform
mdx_better_lists-1.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 32.1 kB

Release files / mdx_better_lists-1.1.0.tar.gz

Download URL mdx_better_lists-1.1.0.tar.gz
Size 23.2 kB
Tags Source
SHA-256 checksum
How to use checksums
02ca2999a5f558bbd56c392b86381cf993497870cd7f11320c990907d1cc3730
BLAKE2b-256 checksum
How to use checksums
0af50b8880572f39e5b2e4fc2a7c1a7adcf70e81fbaabdb35943ed488bc09f71
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Feb 18, 2026.

Transparency log

Release files / mdx_better_lists-1.1.0-py3-none-any.whl

Download URL mdx_better_lists-1.1.0-py3-none-any.whl
Size 8.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
606f9dab04a1b2fb1ccb525753a423c30b17024e17f49e5314e81504f891d9f6
BLAKE2b-256 checksum
How to use checksums
c70dfcd2020b3d518d28c011677aae7b8065070a385b6f908507a2bf599b68d8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Feb 18, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.1.0 This release

2 release files

1.0.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page