Creating a component to generate tabs based on what's in its body is a bit trickier. Here is how JumpStart's TabGroup component does it...
TabTracker into the Environment.Tab (inside SourceCodeTab) records a label and markup in TabTracker instead of in the DOM.tapestry-stitch demonstrates a different technique: to avoid rendering tabs that may never be chosen, tapestry-stitch's TabGroup defers the rendering of each tab until the tab is chosen.
References: Bootstrap Tabs, t:body, Environment.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- We need a doctype to allow us to use special characters like   
     We use a "strict" DTD to make IE follow the alignment rules. -->
     
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd">
<body class="container">
    <h3>Generating Tabs</h3>
    
    <p>Creating a component to generate tabs based on what's in its body is a bit trickier. Here is how JumpStart's <code>TabGroup</code> component does it...</p> 
    
    <ol>
        <li>In beginRender(), TabGroup does not yet know what is in its body, so it pushes a <code>TabTracker</code> into the Environment.</li>
        <li>As the body renders, each <code>Tab</code> (inside <code>SourceCodeTab</code>) records a label and markup in TabTracker <em>instead of in the DOM</em>.</li> 
        <li>In afterRenderBody(...), TabGroup pops the TabTracker from the Environment</li>
        <li>TabGroup renders the tabs and their content from TabTracker's labels and markup.</li>
    </ol>
    
    <p><a href="http://tapestry-stitch.uklance.cloudbees.net/tabgroupdemo">tapestry-stitch</a> demonstrates a 
    different technique: to avoid rendering tabs that may never be chosen, tapestry-stitch's TabGroup defers the rendering of each tab 
    until the tab is chosen.</p>
    
    References: 
    <a href="http://getbootstrap.com/components/#nav-tabs">Bootstrap Tabs</a>, 
    <a href="http://tapestry.apache.org/component-templates.html#ComponentTemplates-The%3Ct%3Abody%3EElement">t:body</a>, 
    <a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/services/Environment.html">Environment</a>.<br/><br/> 
    <t:pagelink page="Index">Home</t:pagelink><br/><br/>
    
    <t:tabgroup>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/navigation/GeneratingTabs.tml"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/navigation/GeneratingTabs.java"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/components/TabGroup.tml"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/components/TabGroup.java"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/models/TabTracker.java"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/components/SourceCodeTab.tml"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/components/SourceCodeTab.java"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/components/Tab.java"/>
    </t:tabgroup>
</body>
</html>
package jumpstart.web.pages.examples.navigation;
public class GeneratingTabs {
}
<!-- Based on Tapestry Stitch's TabGroup (http://tapestry-stitch.uklance.cloudbees.net) 
    and Java Magic's TabPanel (http://tawus.wordpress.com/2011/07/09/a-tab-panel-for-tapestry) . -->
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd" xmlns:p="tapestry:parameter">
<t:content>
    <noscript>
        <div class="alert alert-warning">${message:javascript_required_for_tabs}</div>
    </noscript>
    <!-- We depend on the body not rendering to the DOM, and instead rendering into TabTracker! -->
    <t:body />
    <ul class="nav nav-tabs">
        <t:loop source="tabIds" value="tabId" index="tabNum">
            <li class="${active}">
                <a href="#${tabId}" data-toggle="tab">${tabLabel}</a>
            </li>
        </t:loop>
    </ul>
    <div class="tab-content">
        <t:loop source="tabIds" value="tabId" index="tabNum">
            <div id="${tabId}" class="tab-pane ${active}">
                <!-- Get the rendered markup that was put in TabTracker, above, in t:body. -->
                <t:delegate to="tabMarkup" />
            </div>
        </t:loop>
    </div>
</t:content>
</html>
// Based on Tapestry Stitch's TabGroup (http://tapestry-stitch.uklance.cloudbees.net) 
// and Java Magic's TabPanel (http://tawus.wordpress.com/2011/07/09/a-tab-panel-for-tapestry) .
package jumpstart.web.components;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import jumpstart.web.models.TabTracker;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.runtime.RenderCommand;
import org.apache.tapestry5.runtime.RenderQueue;
import org.apache.tapestry5.services.Environment;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;
public class TabGroup {
    // Screen fields
    @Property
    private List<String> tabIds;
    @Property
    private String tabId;
    @Property
    private int tabNum;
    // Work fields
    private List<String> tabLabels;
    private List<String> tabMarkups;
    // Generally useful bits and pieces
    @Inject
    private Environment environment;
    @Inject
    private JavaScriptSupport javaScriptSupport;
    @Inject
    private ComponentResources componentResources;
    // The code
    /**
     * The tricky part is that we can't render the navbar before we've rendered the body because we don't know how many
     * elements are in the body nor what labels they would like. We solve this by making a TabTracker available to the
     * body. Each Tab in the body will put its label and markup in TabTracker instead of rendering it. Afterwards, in
     * our afterRenderBody(), we will read TabTracker and render the tabs and tab content.
     */
    void beginRender() {
        environment.push(TabTracker.class, new TabTracker());
    }
    /**
     * By the time this method is called, we expect each Tab in the body of this component to have recorded a label and
     * markup in TabTracker instead of rendering it. Using what's in TabTracker we get ready to render tabs and tab
     * content.
     */
    void afterRenderBody(MarkupWriter markupWriter) {
        TabTracker tabTracker = environment.pop(TabTracker.class);
        tabLabels = tabTracker.getLabels();
        tabMarkups = tabTracker.getMarkups();
        // Invent unique ids for each tab.
        tabIds = new ArrayList<>();
        for (int i = 0; i < tabLabels.size(); i++) {
            String id = javaScriptSupport.allocateClientId(componentResources);
            tabIds.add(id);
        }
    }
    void afterRender() {
        // We depend on http://getbootstrap.com/javascript/#tabs . We use its Markup technique.
        javaScriptSupport.require("bootstrap/tab");
    }
    public String getActive() {
        return tabNum == 0 ? "active" : "";
    }
    public String getTabLabel() {
        return tabLabels.get(tabNum);
    }
    public RenderCommand getTabMarkup() {
        return new RenderCommand() {
            public void render(MarkupWriter writer, RenderQueue queue) {
                writer.writeRaw(tabMarkups.get(tabNum));
            }
        };
    }
}
// Based on Tapestry Stitch's TabGroup (http://tapestry-stitch.uklance.cloudbees.net) 
// and Java Magic's TabPanel (http://tawus.wordpress.com/2011/07/09/a-tab-panel-for-tapestry) .
package jumpstart.web.models;
import java.util.ArrayList;
import java.util.List;
public class TabTracker {
    private List<String> labels = new ArrayList<>();
    private List<String> markups = new ArrayList<>();
    public void addTab(String label, String markup) {
        labels.add(label);
        markups.add(markup);
    }
    public List<String> getLabels() {
        return labels;
    }
    public List<String> getMarkups() {
        return markups;
    }
}
<!-- Based on Tapestry Stitch's TabGroup (http://tapestry-stitch.uklance.cloudbees.net) 
    and Java Magic's TabPanel (http://tawus.wordpress.com/2011/07/09/a-tab-panel-for-tapestry). -->
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd" xmlns:p="tapestry:parameter">
<t:content>
    <t:tab label="prop:name">
        <t:sourcecodedisplay src="prop:src" />
    </t:tab>
</t:content>
</html>
// Based on Tapestry Stitch's TabGroup (http://tapestry-stitch.uklance.cloudbees.net) 
// and Java Magic's TabPanel (http://tawus.wordpress.com/2011/07/09/a-tab-panel-for-tapestry).
package jumpstart.web.components;
import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.annotations.Property;
public class SourceCodeTab {
    @Parameter(required = true, defaultPrefix = BindingConstants.LITERAL)
    @Property
    private String src;
    public String getName() {
        return extractSimpleName(src);
    }
    private String extractSimpleName(String path) {
        String simpleName = path;
        int i = path.lastIndexOf("/");
        simpleName = path.substring(i + 1);
        return simpleName;
    }
}
// Based on Tapestry Stitch's TabGroup (http://tapestry-stitch.uklance.cloudbees.net) 
// and Java Magic's TabPanel (http://tawus.wordpress.com/2011/07/09/a-tab-panel-for-tapestry).
package jumpstart.web.components;
import jumpstart.web.models.TabTracker;
import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.annotations.Environmental;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.dom.Element;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.services.Request;
public class Tab {
    @Parameter(required = true, defaultPrefix = BindingConstants.LITERAL, allowNull = false)
    private String label;
    @Inject
    private ComponentResources componentResources;
    @Environmental
    private TabTracker tabTracker;
    @Inject
    private Request request;
    void beforeRenderBody(MarkupWriter writer) {
        // Make a container for the tab body.
        writer.element("div");
    }
    void afterRenderBody(MarkupWriter writer) {
        // End the container and record the label the body's markup in the TabTracker.
        Element bodyContainer = writer.getElement();
        writer.end();
        tabTracker.addTab(label, bodyContainer.getChildMarkup());
        // Remove the container (and therefore the body's markup) from the DOM. TabGroup will render the markup later at
        // its leisure.
        bodyContainer.remove();
    }
}