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.
- 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
pip install mdx_better_listsfrom markdown import markdown
text = """
- Item 1
- Item 2
- Item 3
"""
html = markdown(text, extensions=['mdx_better_lists'])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
}})| 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 |
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 nestedWhen 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> elementWhen 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> elementWhen 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>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>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>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).
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)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
}})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"# 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.
This project follows Test-Driven Development (TDD) principles.
pytest tests/This project is licensed under the MIT License - see the LICENSE file for details.
- Inspired by mdx_truly_sane_lists
- Built on Python-Markdown