Top-level links from a component iframe?

I am using URLs inside streamlit-aggrid table in order to redirect users to pages where they can manipulate an object selected from the table in specific ways.

By default the links open inside the component iframe, when I would like them to open full-sized in the same tab. I can redirect them to a new page with <a href=https://example.com _target="blank">Example</a>, but target="top" or target="parent" do not work at all.

Is this a Streamlit feature, perhaps some sort of security thing? Does anyone know how I could make these attributes work?

Thanks!

1 Like

Hey @ennui,

Have you checked out this thread that seems to cover this issue?

I think you can do this with a HTML link and setting the link target . E.g.:

st.markdown('<a href="..." target="_self">...</a>', unsafe_allow_html=True)

But note that on Streamlit Cloud, this may not work perfectly in some cases. (The app is shown within an iframe on Streamlit Cloud and some websites can’t be opened within an iframe. That’s why we set the default link target to _blank in the first place so that the link always opens in a new tab.)

Hi Caroline,

Thank you for your suggestion. The _blank and _self targets work fine, but they are not what I need:

  • _blank opens a new tab
  • _self opens the link inside the component iframe

I am looking for the link to open at the level of the whole Streamlit interface (outside the component iframe). That would correspond to _top or _parent link targets. Links with those targets do not work at all.

Would you happen to know whether this is an intentional limitation of Streamlit components?

Edit: I am not sure if this maps 100% to how a component like st-aggrid would work, but consider the following reproducible example. For the external link (google.com), only _blank works; for the page link, only _blank and _self work, but _self opens the page inside an iframe so it is not very useful for navigating between pages.

.
├── app.py
└── pages
    └── page.py

app.py:

import streamlit as st
import streamlit.components.v1 as components

components.html(
    """
    <a href='https://google.com' target="_blank">Google _blank</a><br>
    <a href='https://google.com' target="_self">Google _self</a><br>
    <a href='https://google.com' target="_top">Google _top</a><br>
    <a href='https://google.com' target="_parent">Google _parent</a><br>
    """
)

components.html(
    """
    <a href='/page' target="_blank">page _blank</a><br>
    <a href='/page' target="_self">page _self</a><br>
    <a href='/page' target="_top">page _top</a><br>
    <a href='/page' target="_parent">page _parent</a><br>
    """
)

page.py:

import streamlit as st

st.write('Hello world')
1 Like

Hi @ennui I have the exact problem. Did you find a solution?

Hi @no_name no I did not. What is your use case? Personally I am using links inside a streamlit-aggrid table but I hope to transition to the standard dataframe API once this is released and only then see if I need to implement a custom solution.

My use case is using html and javascript to build a carousel component that previews texts, and links to the full texts. I just want the links not to open inside the iframe/html component but on the main tab of the streamlit app. Nothing works for now, except opening up in a new tab

I have the same issue,i made my streamlit-antd-components, and when i click a menu item,i want to link to the streamlit page,but _top or _parent not work,only _blank work,and _self will show refuse connection.

This is hacky, but I’ve grabbed the links from “the real navigation” in order to simulate them getting clicked via a triggered event elsewhere.

In this example, there are three pages ‘a’, ‘b’, and ‘c’

import streamlit as st

js = '''
<script>
    function goTo(page) {
        page_links = parent.document.querySelectorAll('[data-testid="stSidebarNav"] ul li a')
        page_links.forEach((link) => {
            if (link.text == page) {
                link.click()
            }
        })
    }
</script>

<button onclick="goTo('a')">Go to page a</button><br />
<button onclick="goTo('b')">Go to page b</button><br />
<button onclick="goTo('c')">Go to page c</button><br />
'''

st.components.v1.html(js)
1 Like

I did something sort of similar. I had a use case where I needed to directly link to another (external) page so it opens in the same top window that the Streamlit app is currently in, and there was no way to do so.

I’m using the combo of st.markdown and components.html() to create an invisible link on the topmost browser window, and then I trigger its click event whenever the user clicks on the visible link inside the standard Streamlit iframe:

st.markdown(f"""
                    <a id="stripe-btn" href="javascript:void(0);">
                        <div style="
                            display: inline-flex;
                            -webkit-box-align: center;
                            align-items: center;
                            -webkit-box-pack: center;
                            justify-content: center;
                            font-weight: 400;
                            padding: 0.25rem 0.75rem;
                            border-radius: 0.25rem;
                            margin: 0px;
                            line-height: 1.6;
                            width: auto;
                            user-select: none;
                            background-color: #FD504D;
                            color: rgb(255, 255, 255);
                            border: 1px solid rgb(255, 75, 75);
                            text-decoration: none;
                            ">
                            Pay ${price}
                        </div>
                    </a>
                """, unsafe_allow_html=True)

                html(f"""
                    <script>
                    
                        function redirectToStripe() {{
                            window.top.document.getElementById('stripe-link').click();
                        }}
                        window.parent.document.getElementById('stripe-btn').addEventListener("click", function(event) {{
                            redirectToStripe();
                            event.preventDefault();
                        }}, false);
                     
                        // Create iframe element
                        const redirect_link = document.createElement('a');
                        redirect_link.href = '{session.url}';
                        redirect_link.target = '_top';
                        redirect_link.innerText = 'Invisible Link';
                        redirect_link.style = 'display:none;';
                        redirect_link.id = 'stripe-link';
                        window.top.document.body.appendChild(redirect_link);

                    </script>
                """)

Hey this looks promising. Where did you et the session.url from?

Ah yeah, I worded that a bit confusingly. session in this case is literally just a dict that has a url key. It has nothing to do with Streamlit session state. You could replace it with any URL you want to redirect to.

Hi @Caroline , any available solution to set the default link target to _self ?

@snow What situation are you trying to solve exactly? (A button where to go/do what?)

@mathcatsand I tried to use your code, https://tstversion1-8-1.streamlit.app/
buttons don’t direct me to anywhere.
this is the code I used:

import streamlit as st

js = '''
<script>
    function goTo(page) {
        page_links = parent.document.querySelectorAll('[data-testid="stSidebarNav"] ul li a')
        page_links.forEach((link) => {
            if (link.text == page) {
                link.click()
            }
        })
    }
</script>

<button onclick="goTo('https://www.google.com')">Go to page a</button><br />
<button onclick="goTo('https://www.google.com')">Go to page b</button><br />
<button onclick="goTo('https://www.google.com')">Go to page c</button><br />
'''

st.components.v1.html(js)

what I am trying to do is to create a button that directs the user to www.google.com page in the same tab (target=_self)