Repository: EmilStenstrom/django-components

Issue:
Refactor accessing of default slot from `as var` to kwarg
See https://github.com/EmilStenstrom/django-components/pull/495#discussion_r1607117487

Discussion:
JuroOravec: @EmilStenstrom Thanks for the review! Good catch with the breaking change, I've added a note.

Patch:
diff --git a/README.md b/README.md
index ca94ae6..b65cbdb 100644
--- a/README.md
+++ b/README.md
@@ -44,6 +44,12 @@ Read on to learn about the details!
 
 ## Release notes
 
+🚨📢 **Version 0.77** CHANGED the syntax for accessing default slot content.
+- Previously, the syntax was
+`{% fill "my_slot" as "alias" %}` and `{{ alias.default }}`.
+- Now, the syntax is
+`{% fill "my_slot" default="alias" %}` and `{{ alias }}`.
+
 **Version 0.74** introduces `html_attrs` tag and `prefix:key=val` construct for passing dicts to components.
 
 🚨📢 **Version 0.70**
@@ -627,13 +633,26 @@ Which you can then use are regular default slot:
 
 _Added in version 0.26_
 
-Certain properties of a slot can be accessed from within a 'fill' context. They are provided as attributes on a user-defined alias of the targeted slot. For instance, let's say you're filling a slot called 'body'. To access properties of this slot, alias it using the 'as' keyword to a new name -- or keep the original name. With the new slot alias, you can call `<alias>.default` to insert the default content.
+> NOTE: In version 0.77, the syntax was changed from
+> ```django
+> {% fill "my_slot" as "alias" %} {{ alias.default }}
+> ```
+> to
+> ```django
+> {% fill "my_slot" default="slot_default" %} {{ slot_default }}
+> ```
+
+Sometimes you may want to keep the original slot, but only wrap or prepend/append content to it. To do so, you can access the default slot via the `default` kwarg.
+
+Similarly to the `data` attribute, you specify the variable name through which the default slot will be made available. 
 
-Notice the use of `as "body"` below:
+For instance, let's say you're filling a slot called 'body'. To render the original slot, assign it to a variable using the `'default'` keyword. You then render this variable to insert the default content:
 
 ```htmldjango
 {% component "calendar" date="2020-06-06" %}
-    {% fill "body" as "body" %}{{ body.default }}. Have a great day!{% endfill %}
+    {% fill "body" default="body_default" %}
+        {{ body_default }}. Have a great day!
+    {% endfill %}
 {% endcomponent %}
 ```
 
@@ -832,12 +851,12 @@ While this does not:
 ```
 
 Note: You cannot set the `data` attribute and
-[slot alias (`as var` syntax)](#accessing-original-content-of-slots)
+[`default` attribute)](#accessing-original-content-of-slots)
 to the same name. This raises an error:
 
 ```django
 {% component "my_comp" %}
-    {% fill "content" data="slot_var" as "slot_var" %}
+    {% fill "content" data="slot_var" default="slot_var" %}
         {{ slot_var.input }}
     {% endfill %}
 {% endcomponent %}
diff --git a/src/django_components/component.py b/src/django_components/component.py
index 9a5cf82..7f712a6 100644
--- a/src/django_components/component.py
+++ b/src/django_components/component.py
@@ -358,8 +358,8 @@ class Component(View, metaclass=SimplifiedInterfaceMediaDefiningClass):
         slot_fills = {
             slot_name: FillContent(
                 nodes=NodeList([TextNode(escape(content) if escape_content else content)]),
-                alias=None,
-                scope=None,
+                slot_default_var=None,
+                slot_data_var=None,
             )
             for (slot_name, content) in slots_data.items()
         }
@@ -422,12 +422,12 @@ class ComponentNode(Node):
                         f"Detected duplicate fill tag name '{resolved_name}'."
                     )
 
-                resolved_fill_alias = fill_node.resolve_alias(context, resolved_component_name)
-                resolved_scope_var = fill_node.resolve_scope(context, resolved_component_name)
+                resolved_slot_default_var = fill_node.resolve_slot_default(context, resolved_component_name)
+                resolved_slot_data_var = fill_node.resolve_slot_data(context, resolved_component_name)
                 fill_content[resolved_name] = FillContent(
                     nodes=fill_node.nodelist,
-                    alias=resolved_fill_alias,
-                    scope=resolved_scope_var,
+                    slot_default_var=resolved_slot_default_var,
+                    slot_data_var=resolved_slot_data_var,
                 )
 
         component: Component = component_cls(
diff --git a/src/django_components/slots.py b/src/django_components/slots.py
index 962dec2..e5dfe51 100644
--- a/src/django_components/slots.py
+++ b/src/django_components/slots.py
@@ -24,8 +24,8 @@ DEFAULT_SLOT_KEY = "_DJANGO_COMPONENTS_DEFAULT_SLOT"
 
 SlotId = str
 SlotName = str
-AliasName = str
-ScopeName = str
+SlotDefaultName = str
+SlotDataName = str
 
 
 class FillContent(NamedTuple):
@@ -44,8 +44,8 @@ class FillContent(NamedTuple):
     """
 
     nodes: NodeList
-    alias: Optional[AliasName]
-    scope: Optional[ScopeName]
+    slot_default_var: Optional[SlotDefaultName]
+    slot_data_var: Optional[SlotDataName]
 
 
 class Slot(NamedTuple):
@@ -80,27 +80,26 @@ class SlotFill(NamedTuple):
     is_filled: bool
     nodelist: NodeList
     context_data: Dict
-    alias: Optional[AliasName]
-    scope: Optional[ScopeName]
+    slot_default_var: Optional[SlotDefaultName]
+    slot_data_var: Optional[SlotDataName]
 
 
-class UserSlotVar:
+class LazySlot:
     """
-    Extensible mechanism for offering 'fill' blocks in template access to properties
-    of parent slot.
+    Bound a slot and a context, so we can move these around as a single variable,
+    and lazily render the slot with given context only once coerced to string.
 
-    How it works: At render time, SlotNode(s) that have been aliased in the fill tag
-    of the component instance create an instance of UserSlotVar. This instance is made
-    available to the rendering context on a key matching the slot alias (see
-    SlotNode.render() for implementation).
+    This is used to access slots as variables inside the templates. When a LazySlot
+    is rendered in the template with `{{ my_lazy_slot }}`, it will output the contents
+    of the slot.
     """
 
     def __init__(self, slot: "SlotNode", context: Context):
         self._slot = slot
         self._context = context
 
-    @property
-    def default(self) -> str:
+    # Render the slot when the template coerces LazySlot to string
+    def __str__(self) -> str:
         return mark_safe(self._slot.nodelist.render(self._context))
 
 
@@ -141,20 +140,28 @@ class SlotNode(Node):
 
         extra_context: Dict[str, Any] = {}
 
-        # If slot is using alias `{% slot "myslot" as "abc" %}`, then set the "abc" to
-        # the context, so users can refer to the slot from within the slot.
-        if slot_fill.alias:
-            if not slot_fill.alias.isidentifier():
-                raise TemplateSyntaxError(f"Invalid fill alias. Must be a valid identifier. Got '{slot_fill.alias}'")
-            extra_context[slot_fill.alias] = UserSlotVar(self, context)
+        # If slot fill is using `{% fill "myslot" default="abc" %}`, then set the "abc" to
+        # the context, so users can refer to the default slot from within the fill content.
+        default_var = slot_fill.slot_default_var
+        if default_var:
+            if not default_var.isidentifier():
+                raise TemplateSyntaxError(
+                    f"Slot default alias in fill '{self.name}' must be a valid identifier. Got '{default_var}'"
+                )
+            extra_context[default_var] = LazySlot(self, context)
 
         # Expose the kwargs that were passed to the `{% slot %}` tag. These kwargs
         # are made available through a variable name that was set on the `{% fill %}`
         # tag.
-        if slot_fill.scope:
+        data_var = slot_fill.slot_data_var
+        if data_var:
+            if not data_var.isidentifier():
+                raise TemplateSyntaxError(
+                    f"Slot data alias in fill '{self.name}' must be a valid identifier. Got '{data_var}'"
+                )
             slot_kwargs = safe_resolve_dict(self.slot_kwargs, context)
             slot_kwargs = process_aggregate_kwargs(slot_kwargs)
-            extra_context[slot_fill.scope] = slot_kwargs
+            extra_context[data_var] = slot_kwargs
 
         # For the user-provided slot fill, we want to use the context of where the slot
         # came from (or current context if configured so)
@@ -192,17 +199,17 @@ class FillNode(Node):
         self,
         nodelist: NodeList,
         name_fexp: FilterExpression,
-        alias_fexp: Optional[FilterExpression] = None,
-        scope_fexp: Optional[FilterExpression] = None,
+        slot_default_var_fexp: Optional[FilterExpression] = None,
+        slot_data_var_fexp: Optional[FilterExpression] = None,
         is_implicit: bool = False,
         node_id: Optional[str] = None,
     ):
         self.node_id = node_id or gen_id()
         self.nodelist = nodelist
         self.name_fexp = name_fexp
-        self.alias_fexp = alias_fexp
+        self.slot_default_var_fexp = slot_default_var_fexp
         self.is_implicit = is_implicit
-        self.scope_fexp = scope_fexp
+        self.slot_data_var_fexp = slot_data_var_fexp
         self.component_id: Optional[str] = None
 
     def render(self, context: Context) -> str:
@@ -215,11 +222,11 @@ class FillNode(Node):
     def __repr__(self) -> str:
         return f"<{type(self)} Name: {self.name_fexp}. Contents: {repr(self.nodelist)}.>"
 
-    def resolve_alias(self, context: Context, component_name: Optional[str] = None) -> Optional[str]:
-        return self.resolve_fexp("alias", self.alias_fexp, context, component_name)
+    def resolve_slot_default(self, context: Context, component_name: Optional[str] = None) -> Optional[str]:
+        return self.resolve_fexp("slot default", self.slot_default_var_fexp, context, component_name)
 
-    def resolve_scope(self, context: Context, component_name: Optional[str] = None) -> Optional[str]:
-        return self.resolve_fexp("scope", self.scope_fexp, context, component_name)
+    def resolve_slot_data(self, context: Context, component_name: Optional[str] = None) -> Optional[str]:
+        return self.resolve_fexp("slot data", self.slot_data_var_fexp, context, component_name)
 
     def resolve_fexp(
         self,
@@ -232,14 +239,14 @@ class FillNode(Node):
             return None
 
         try:
-            resolved_alias = resolve_expression_as_identifier(context, fexp)
+            resolved_name = resolve_expression_as_identifier(context, fexp)
         except ValueError as err:
             raise TemplateSyntaxError(
                 f"Fill tag {name} '{fexp.var}' in component {component_name}"
                 f"does not resolve to a valid Python identifier."
             ) from err
 
-        return resolved_alias
+        return resolved_name
 
 
 def parse_slot_fill_nodes_from_component_nodelist(
@@ -363,8 +370,8 @@ def resolve_slots(
             is_filled=True,
             nodelist=fill.nodes,
             context_data=context_data,
-            alias=fill.alias,
-            scope=fill.scope,
+            slot_default_var=fill.slot_default_var,
+            slot_data_var=fill.slot_data_var,
         )
         for name, fill in fill_content.items()
     }
@@ -452,8 +459,8 @@ def resolve_slots(
                 is_filled=False,
                 nodelist=slot.nodelist,
                 context_data=context_data,
-                alias=None,
-                scope=None,
+                slot_default_var=None,
+                slot_data_var=None,
             )
             # Since the slot's default CAN include other slots (because it's defined in
             # the same template), we need to enqueue the slot's children
@@ -500,8 +507,8 @@ def _resolve_default_slot(
                     is_filled=default_fill.is_filled,
                     nodelist=default_fill.nodelist,
                     context_data=default_fill.context_data,
-                    alias=default_fill.alias,
-                    scope=default_fill.scope,
+                    slot_default_var=default_fill.slot_default_var,
+                    slot_data_var=default_fill.slot_data_var,
                     # Updated fields
                     name=slot.name,
                     escaped_name=_escape_slot_name(slot.name),
diff --git a/src/django_components/templatetags/component_tags.py b/src/django_components/templatetags/component_tags.py
index 6f342a6..45cf50d 100644
--- a/src/django_components/templatetags/component_tags.py
+++ b/src/django_components/templatetags/component_tags.py
@@ -31,6 +31,7 @@ register = django.template.Library()
 SLOT_REQUIRED_OPTION_KEYWORD = "required"
 SLOT_DEFAULT_OPTION_KEYWORD = "default"
 SLOT_DATA_ATTR = "data"
+SLOT_DEFAULT_ATTR = "default"
 
 
 def get_components_from_registry(registry: ComponentRegistry) -> List["Component"]:
@@ -147,7 +148,7 @@ def do_fill(parser: Parser, token: Token) -> FillNode:
     """
     # e.g. {% fill <name> %}
     tag_name, *args = token.split_contents()
-    slot_name_fexp, alias_fexp, scope_var_fexp = _parse_fill_args(parser, args, tag_name)
+    slot_name_fexp, slot_default_var_fexp, slot_data_var_fexp = _parse_fill_args(parser, args, tag_name)
 
     # Use a unique ID to be able to tie the fill nodes with components and slots
     # NOTE: MUST be called BEFORE `parser.parse()` to ensure predictable numbering
@@ -160,8 +161,8 @@ def do_fill(parser: Parser, token: Token) -> FillNode:
     fill_node = FillNode(
         nodelist,
         name_fexp=slot_name_fexp,
-        alias_fexp=alias_fexp,
-        scope_fexp=scope_var_fexp,
+        slot_default_var_fexp=slot_default_var_fexp,
+        slot_data_var_fexp=slot_data_var_fexp,
         node_id=fill_id,
     )
 
@@ -382,19 +383,13 @@ def _parse_fill_args(
 ) -> Tuple[FilterExpression, Optional[FilterExpression], Optional[FilterExpression]]:
     if not len(bits):
         raise TemplateSyntaxError(
-            "'fill' tag does not match pattern " f"{{% fill <name> [{SLOT_DATA_ATTR}=val] [as alias] %}}. "
+            "'fill' tag does not match pattern "
+            f"{{% fill <name> [{SLOT_DATA_ATTR}=val] [{SLOT_DEFAULT_ATTR}=slot_var] %}} "
         )
 
     slot_name = bits.pop(0)
     slot_name_fexp = parser.compile_filter(slot_name)
 
-    alias_fexp: Optional[FilterExpression] = None
-    # e.g. {% fill <name> as <alias> %}
-    if len(bits) >= 2 and bits[-2].lower() == "as":
-        alias = bits.pop()
-        bits.pop()  # Remove the "as" keyword
-        alias_fexp = parser.compile_filter(alias)
-
     # Even tho we want to parse only single kwarg, we use the same logic for parsing
     # as we use for other tags, for consistency.
     _, tag_kwarg_pairs = parse_bits(
@@ -410,17 +405,29 @@ def _parse_fill_args(
             raise TemplateSyntaxError(f"'{tag_name}' received multiple values for keyword argument '{key}'")
         tag_kwargs[key] = val
 
-    scope_var_fexp: Optional[FilterExpression] = None
+    # Extract known kwargs
+    slot_data_var_fexp: Optional[FilterExpression] = None
     if SLOT_DATA_ATTR in tag_kwargs:
-        scope_var_fexp = tag_kwargs.pop(SLOT_DATA_ATTR)
-        if not is_wrapped_in_quotes(scope_var_fexp.token):
+        slot_data_var_fexp = tag_kwargs.pop(SLOT_DATA_ATTR)
+        if not is_wrapped_in_quotes(slot_data_var_fexp.token):
+            raise TemplateSyntaxError(
+                f"Value of '{SLOT_DATA_ATTR}' in '{tag_name}' tag must be a string literal, got '{slot_data_var_fexp}'"
+            )
+
+    slot_default_var_fexp: Optional[FilterExpression] = None
+    if SLOT_DEFAULT_ATTR in tag_kwargs:
+        slot_default_var_fexp = tag_kwargs.pop(SLOT_DEFAULT_ATTR)
+        if not is_wrapped_in_quotes(slot_default_var_fexp.token):
             raise TemplateSyntaxError(
-                f"Value of '{SLOT_DATA_ATTR}' in '{tag_name}' tag must be a string literal, got '{scope_var_fexp}'"
+                f"Value of '{SLOT_DEFAULT_ATTR}' in '{tag_name}' tag must be a string literal,"
+                f" got '{slot_default_var_fexp}'"
             )
 
-    if scope_var_fexp and alias_fexp and scope_var_fexp.token == alias_fexp.token:
+    # data and default cannot be bound to the same variable
+    if slot_data_var_fexp and slot_default_var_fexp and slot_data_var_fexp.token == slot_default_var_fexp.token:
         raise TemplateSyntaxError(
-            f"'{tag_name}' received the same string for slot alias (as ...) and slot data ({SLOT_DATA_ATTR}=...)"
+            f"'{tag_name}' received the same string for slot default ({SLOT_DEFAULT_ATTR}=...)"
+            f" and slot data ({SLOT_DATA_ATTR}=...)"
         )
 
     if len(tag_kwargs):
@@ -428,7 +435,7 @@ def _parse_fill_args(
         extra_keys = ", ".join(extra_keywords)
         raise TemplateSyntaxError(f"'{tag_name}' received unexpected kwargs: {extra_keys}")
 
-    return slot_name_fexp, alias_fexp, scope_var_fexp
+    return slot_name_fexp, slot_default_var_fexp, slot_data_var_fexp
 
 
 def _get_positional_param(


Test patch:
diff --git a/tests/test_templatetags.py b/tests/test_templatetags.py
index 3155365..d5dafe7 100644
--- a/tests/test_templatetags.py
+++ b/tests/test_templatetags.py
@@ -912,7 +912,7 @@ class ConditionalSlotTests(BaseTestCase):
         self.assertHTMLEqual(rendered, '<p id="a">Override A</p><p id="b">Override B</p>')
 
 
-class SlotSuperTests(BaseTestCase):
+class SlotDefaultTests(BaseTestCase):
     @classmethod
     def setUpClass(cls):
         super().setUpClass()
@@ -924,13 +924,13 @@ class SlotSuperTests(BaseTestCase):
         super().tearDownClass()
         component.registry.clear()
 
-    def test_basic_super_functionality(self):
+    def test_basic(self):
         template_str: types.django_html = """
             {% load component_tags %}
             {% component "test" %}
-                {% fill "header" as "header" %}Before: {{ header.default }}{% endfill %}
-                {% fill "main" as "main" %}{{ main.default }}{% endfill %}
-                {% fill "footer" as "footer" %}{{ footer.default }}, after{% endfill %}
+                {% fill "header" default="header" %}Before: {{ header }}{% endfill %}
+                {% fill "main" default="main" %}{{ main }}{% endfill %}
+                {% fill "footer" default="footer" %}{{ footer }}, after{% endfill %}
             {% endcomponent %}
         """
         template = Template(template_str)
@@ -947,13 +947,13 @@ class SlotSuperTests(BaseTestCase):
             """,
         )
 
-    def test_multiple_super_calls(self):
+    def test_multiple_calls(self):
         template_str: types.django_html = """
             {% load component_tags %}
             {% component "test" %}
-                {% fill "header" as "header" %}
-                    First: {{ header.default }};
-                    Second: {{ header.default }}
+                {% fill "header" default="header" %}
+                    First: {{ header }};
+                    Second: {{ header }}
                 {% endfill %}
             {% endcomponent %}
         """
@@ -971,14 +971,16 @@ class SlotSuperTests(BaseTestCase):
         """,
         )
 
-    def test_super_under_if_node(self):
+    def test_under_if_and_forloop(self):
         template_str: types.django_html = """
             {% load component_tags %}
             {% component "test" %}
-                {% fill "header" as "header" %}
+                {% fill "header" default="header" %}
                     {% for i in range %}
-                        {% if forloop.first %}First {{ header.default }}
-                        {% else %}Later {{ header.default }}
+                        {% if forloop.first %}
+                            First {{ header }}
+                        {% else %}
+                            Later {{ header }}
                         {% endif %}
                     {% endfor %}
                 {% endfill %}
@@ -998,6 +1000,52 @@ class SlotSuperTests(BaseTestCase):
             """,
         )
 
+    def test_nested_fills(self):
+        template_str: types.django_html = """
+            {% load component_tags %}
+            {% component "test" %}
+                {% fill "header" default="header1" %}
+                    header1_in_header1: {{ header1 }}
+                    {% component "test" %}
+                        {% fill "header" default="header2" %}
+                            header1_in_header2: {{ header1 }}
+                            header2_in_header2: {{ header2 }}
+                        {% endfill %}
+                        {% fill "footer" default="footer2" %}
+                            header1_in_footer2: {{ header1 }}
+                            footer2_in_footer2: {{ footer2 }}
+                        {% endfill %}
+                    {% endcomponent %}
+                {% endfill %}
+            {% endcomponent %}
+        """
+        template = Template(template_str)
+        rendered = template.render(Context({}))
+
+        self.assertHTMLEqual(
+            rendered,
+            """
+            <custom-template>
+                <header>
+                    header1_in_header1: Default header
+                    <custom-template>
+                        <header>
+                            header1_in_header2: Default header
+                            header2_in_header2: Default header
+                        </header>
+                        <main>Default main</main>
+                        <footer>
+                            header1_in_footer2: Default header
+                            footer2_in_footer2: Default footer
+                        </footer>
+                    </custom-template>
+                </header>
+                <main>Default main</main>
+                <footer>Default footer</footer>
+            </custom-template>
+            """,
+        )
+
 
 class TemplateSyntaxErrorTests(BaseTestCase):
     @classmethod
@@ -1310,11 +1358,11 @@ class ComponentNestingTests(BaseTestCase):
         self.assertHTMLEqual(rendered, expected)
 
     @override_settings(COMPONENTS={"context_behavior": "django"})
-    def test_component_nesting_component_with_fill_and_super__django(self):
+    def test_component_nesting_component_with_slot_default__django(self):
         template_str: types.django_html = """
             {% load component_tags %}
             {% component "dashboard" %}
-              {% fill "header" as "h" %} Hello! {{ h.default }} {% endfill %}
+              {% fill "header" default="h" %} Hello! {{ h }} {% endfill %}
             {% endcomponent %}
         """
         template = Template(template_str)
@@ -1338,11 +1386,11 @@ class ComponentNestingTests(BaseTestCase):
         self.assertHTMLEqual(rendered, expected)
 
     @override_settings(COMPONENTS={"context_behavior": "isolated"})
-    def test_component_nesting_component_with_fill_and_super__isolated(self):
+    def test_component_nesting_component_with_slot_default__isolated(self):
         template_str: types.django_html = """
             {% load component_tags %}
             {% component "dashboard" %}
-              {% fill "header" as "h" %} Hello! {{ h.default }} {% endfill %}
+              {% fill "header" default="h" %} Hello! {{ h }} {% endfill %}
             {% endcomponent %}
         """
         template = Template(template_str)
@@ -2194,8 +2242,8 @@ class IterationFillTest(BaseTestCase):
             {% component "slot_in_a_loop" objects=objects %}
                 {% fill "slot_inner" %}
                     {% component "slot_in_a_loop" objects=object.inner %}
-                        {% fill "slot_inner" as "super_slot_inner" %}
-                            {{ super_slot_inner.default }}
+                        {% fill "slot_inner" default="super_slot_inner" %}
+                            {{ super_slot_inner }}
                         {% endfill %}
                     {% endcomponent %}
                 {% endfill %}
@@ -2228,8 +2276,8 @@ class IterationFillTest(BaseTestCase):
             {% component "slot_in_a_loop" objects=objects %}
                 {% fill "slot_inner" %}
                     {% component "slot_in_a_loop" objects=object.inner %}
-                        {% fill "slot_inner" as "super_slot_inner" %}
-                            {{ super_slot_inner.default }}
+                        {% fill "slot_inner" default="super_slot_inner" %}
+                            {{ super_slot_inner }}
                         {% endfill %}
                     {% endcomponent %}
                 {% endfill %}
@@ -2256,9 +2304,9 @@ class IterationFillTest(BaseTestCase):
                 {% fill "slot_inner" %}
                     {{ outer_scope_variable_1 }}
                     {% component "slot_in_a_loop" objects=object.inner %}
-                        {% fill "slot_inner" as "super_slot_inner" %}
+                        {% fill "slot_inner" default="super_slot_inner" %}
                             {{ outer_scope_variable_2 }}
-                            {{ super_slot_inner.default }}
+                            {{ super_slot_inner }}
                         {% endfill %}
                     {% endcomponent %}
                 {% endfill %}
@@ -2311,9 +2359,9 @@ class IterationFillTest(BaseTestCase):
                 {% fill "slot_inner" %}
                     {{ outer_scope_variable_1 }}
                     {% component "slot_in_a_loop" objects=object.inner %}
-                        {% fill "slot_inner" as "super_slot_inner" %}
+                        {% fill "slot_inner" default="super_slot_inner" %}
                             {{ outer_scope_variable_2 }}
-                            {{ super_slot_inner.default }}
+                            {{ super_slot_inner }}
                         {% endfill %}
                     {% endcomponent %}
                 {% endfill %}
@@ -2358,9 +2406,9 @@ class IterationFillTest(BaseTestCase):
                 {% fill "slot_inner" %}
                     {{ outer_scope_variable_1|safe }}
                     {% component "slot_in_a_loop" objects=objects %}
-                        {% fill "slot_inner" as "super_slot_inner" %}
+                        {% fill "slot_inner" default="super_slot_inner" %}
                             {{ outer_scope_variable_2|safe }}
-                            {{ super_slot_inner.default }}
+                            {{ super_slot_inner }}
                         {% endfill %}
                     {% endcomponent %}
                 {% endfill %}
@@ -2401,7 +2449,7 @@ class ScopedSlotTest(BaseTestCase):
             template: types.django_html = """
                 {% load component_tags %}
                 <div>
-                    {% slot "my_slot" abc=abc def=var123 %}Default text{% endslot %}
+                    {% slot "my_slot" abc=abc var123=var123 %}Default text{% endslot %}
                 </div>
             """
 
@@ -2416,7 +2464,7 @@ class ScopedSlotTest(BaseTestCase):
             {% component "test" %}
                 {% fill "my_slot" data="slot_data_in_fill" %}
                     {{ slot_data_in_fill.abc }}
-                    {{ slot_data_in_fill.def }}
+                    {{ slot_data_in_fill.var123 }}
                 {% endfill %}
             {% endcomponent %}
         """
@@ -2435,7 +2483,7 @@ class ScopedSlotTest(BaseTestCase):
             template: types.django_html = """
                 {% load component_tags %}
                 <div>
-                    {% slot "my_slot" default abc=abc 123=var123 required %}Default text{% endslot %}
+                    {% slot "my_slot" default abc=abc var123=var123 required %}Default text{% endslot %}
                 </div>
             """
 
@@ -2450,7 +2498,7 @@ class ScopedSlotTest(BaseTestCase):
             {% component "test" %}
                 {% fill "my_slot" data="slot_data_in_fill" %}
                     {{ slot_data_in_fill.abc }}
-                    {{ slot_data_in_fill.123 }}
+                    {{ slot_data_in_fill.var123 }}
                 {% endfill %}
             {% endcomponent %}
         """
@@ -2463,13 +2511,13 @@ class ScopedSlotTest(BaseTestCase):
         """
         self.assertHTMLEqual(rendered, expected)
 
-    def test_slot_data_fill_with_as(self):
+    def test_slot_data_with_slot_default(self):
         @component.register("test")
         class TestComponent(component.Component):
             template: types.django_html = """
                 {% load component_tags %}
                 <div>
-                    {% slot "my_slot" abc=abc 123=var123 %}Default text{% endslot %}
+                    {% slot "my_slot" abc=abc var123=var123 %}Default text{% endslot %}
                 </div>
             """
 
@@ -2482,10 +2530,10 @@ class ScopedSlotTest(BaseTestCase):
         template: types.django_html = """
             {% load component_tags %}
             {% component "test" %}
-                {% fill "my_slot" data="slot_data_in_fill" as "slot_var" %}
-                    {{ slot_var.default }}
+                {% fill "my_slot" data="slot_data_in_fill" default="slot_var" %}
+                    {{ slot_var }}
                     {{ slot_data_in_fill.abc }}
-                    {{ slot_data_in_fill.123 }}
+                    {{ slot_data_in_fill.var123 }}
                 {% endfill %}
             {% endcomponent %}
         """
@@ -2499,13 +2547,13 @@ class ScopedSlotTest(BaseTestCase):
         """
         self.assertHTMLEqual(rendered, expected)
 
-    def test_slot_data_raises_on_slot_data_and_as_same_var(self):
+    def test_slot_data_raises_on_slot_data_and_slot_default_same_var(self):
         @component.register("test")
         class TestComponent(component.Component):
             template: types.django_html = """
                 {% load component_tags %}
                 <div>
-                    {% slot "my_slot" abc=abc 123=var123 %}Default text{% endslot %}
+                    {% slot "my_slot" abc=abc var123=var123 %}Default text{% endslot %}
                 </div>
             """
 
@@ -2518,14 +2566,14 @@ class ScopedSlotTest(BaseTestCase):
         template: types.django_html = """
             {% load component_tags %}
             {% component "test" %}
-                {% fill "my_slot" data="slot_var" as "slot_var" %}
-                    {{ slot_var.default }}
+                {% fill "my_slot" data="slot_var" default="slot_var" %}
+                    {{ slot_var }}
                 {% endfill %}
             {% endcomponent %}
         """
         with self.assertRaisesMessage(
             TemplateSyntaxError,
-            "'fill' received the same string for slot alias (as ...) and slot data (data=...)",
+            "'fill' received the same string for slot default (default=...) and slot data (data=...)",
         ):
             Template(template).render(Context())
 
@@ -2535,7 +2583,7 @@ class ScopedSlotTest(BaseTestCase):
             template: types.django_html = """
                 {% load component_tags %}
                 <div>
-                    {% slot "my_slot" abc=abc 123=var123 %}Default text{% endslot %}
+                    {% slot "my_slot" abc=abc var123=var123 %}Default text{% endslot %}
                 </div>
             """
 
@@ -2585,7 +2633,7 @@ class ScopedSlotTest(BaseTestCase):
             template: types.django_html = """
                 {% load component_tags %}
                 <div>
-                    {% slot "my_slot" abc=abc 123=var123 %}Default text{% endslot %}
+                    {% slot "my_slot" abc=abc var123=var123 %}Default text{% endslot %}
                 </div>
             """
 
@@ -2603,3 +2651,49 @@ class ScopedSlotTest(BaseTestCase):
         rendered = Template(template).render(Context())
         expected = "<div> Default text </div>"
         self.assertHTMLEqual(rendered, expected)
+
+    def test_nested_fills(self):
+        @component.register("test")
+        class TestComponent(component.Component):
+            template: types.django_html = """
+                {% load component_tags %}
+                <div>
+                    {% slot "my_slot" abc=abc input=input %}Default text{% endslot %}
+                </div>
+            """
+
+            def get_context_data(self, input):
+                return {
+                    "abc": "def",
+                    "input": input,
+                }
+
+        template_str: types.django_html = """
+            {% load component_tags %}
+            {% component "test" input=1 %}
+                {% fill "my_slot" data="data1" %}
+                    data1_in_slot1: {{ data1|safe }}
+                    {% component "test" input=2 %}
+                        {% fill "my_slot" data="data2" %}
+                            data1_in_slot2: {{ data1|safe }}
+                            data2_in_slot2: {{ data2|safe }}
+                        {% endfill %}
+                    {% endcomponent %}
+                {% endfill %}
+            {% endcomponent %}
+        """
+        template = Template(template_str)
+        rendered = template.render(Context({}))
+
+        self.assertHTMLEqual(
+            rendered,
+            """
+            <div>
+                data1_in_slot1: {'abc': 'def', 'input': 1}
+                <div>
+                    data1_in_slot2: {'abc': 'def', 'input': 1}
+                    data2_in_slot2: {'abc': 'def', 'input': 2}
+                </div>
+            </div>
+            """,
+        )