Recursively Building and Editing a Tree of Categories/Choices

Hello everyone!

I wanted to come forward with a problem I am having with my UX design, and see what advice people had on the matter.

Context:
Within my application, the user is able to create a list of categories/choices (these terms are interchangeable in my context). These choices can also have sub-choices, and a certain amount of allowed sub-levels. Effectively, I am allowing them to build a tree of choices with n levels, where n will be set by me at some point for the sake of not allowing too deep of a tree.

The way I have my data serialized is a JSON structure, containing a list of all top level nodes, and then each node as a “children” attribute, which contains more nodes with the same structure. This is then stored in an optional JSON field of a generic model object. Here is an example of a tree in this structure:

[{"name": "1", "children": [{"name": "1.1", "children": [{"name": "1.1.1", "children": []}]}]}, {"name": "2", "children": []}]

Problem:
I need a good way to handle the user building this and the problem lies in the demand for a recursive approach. I have attached my hardcoded solution with an image to help you better understand what I am aiming for. As you can see from my HTML, I rely heavily on Alpine directives and javascript functions to maintain the list as is. But how could I build a template that renders each level recursively and maintains the functionality of the add and delete buttons for their assigned nodes?

tree_choices_build.html

<div x-data="treeBuilder({{initialTree}})">
    <h2 class="text-xl font-bold my-4">Manage Tree Choices</h2>
    <div class="my-4">
        <button type="button" @click="addNode(null)" class="bg-blue-500 text-white px-4 py-2 rounded">+</button>
    </div>
    <div id="tree" class="my-4">
        <template x-for="(node, index) in tree" :key="index">
            <div class="ml-4 border-l-2 pl-2">
                <input type="text" x-model="node.name" placeholder="Category Name" class="border rounded px-2 py-1">
                <button type="button" @click="addNode(index)" class="bg-green-500 text-white px-2 py-1 rounded-full ml-2"> + </button>
                <button type="button" @click="removeNode(index)" class="bg-red-500 text-white px-2 py-1 rounded-full ml-2"> - </button>
                
                <div x-show="node.children.length > 0" class="ml-4 mt-2" x-transition>
                    <template x-for="(child, childIndex) in node.children" :key="childIndex">
                        <div class="ml-4 border-l-2 pl-2">
                            <input type="text" x-model="child.name" placeholder="Subcategory Name" class="border rounded px-2 py-1">
                            <button type="button" @click="removeNode(index, childIndex)" class="bg-red-500 text-white px-2 py-1 rounded-full ml-2"> - </button>
                        </div>
                    </template>
                </div>
            </div>
        </template>
    </div>
    <input type="hidden" name="tree_json" :value="JSON.stringify(tree)">
</div>
<script>
    function treeBuilder(initialTree = []) {
        return {
            tree: initialTree.length ? initialTree : [],
            addNode(parentIndex = null, childIndex = null) {
                if (parentIndex === null) {
                    this.tree.push({ name: '', children: [] });
                } else if (childIndex === null) {
                    this.tree[parentIndex].children.push({ name: '', children: [] });
                } else {
                    this.tree[parentIndex].children[childIndex].children.push({ name: '', children: [] });
                }
            },
            removeNode(parentIndex, childIndex = null) {
                if (childIndex === null) {
                    this.tree.splice(parentIndex, 1);
                } else {
                    this.tree[parentIndex].children.splice(childIndex, 1);
                }
            }
        }
    }
</script>

Current UI

I feel this is a lack of knowledge and experience on my end, but I’m also worried this is a limitation of my current FE tech stack (Django Templates, AlpineJS, HTMX, TailwindCSS). Any recommendations, critiques, etc. are more than welcome!!! I am also willing to bring in Javascript tools/libraries if necessary. Thank you in advance!

Disclaimer:
I apologize if this may not be an appropriate place to post a question like this, please correct me if so.

You can do this by looping through the nodes. The nodes should have forms with unique submit ids for each level or category. Then use request queries to update the forms as per the add/delete buttons. That is assuming you are not using js. Otherwise, your js should be able to complete the magic without url queries.