As far as I can tell, Dash does not provide a text input element that grows vertically as the text wraps. The only way I can think to pull this off is to use a callback which calculates how many times the text has wrapped in the input element as text is entered, then update the TextArea height accordingly. Is this feasible? Or is there a built-in means of pulling this off? Thanks for your help!
Welcome back to the forum @adiadidas15
Sigh, I wish the browser had built-in ways to do this better. Turns out it’s actually one of those dark corners of HTML without a native solution, so folks have been working on different workarounds six ways to sunday. Here’s a post explaining some of them, including a CSS solution with a JS one-liner at the very end that some commenters proclaim as “genius” . Getting that JS snippet to run after your text area has rendered might take a little bit of finessing though… will probably need to set a
setInterval
that continuously checks and waits for the element to appear…
Here’s an example based on the clever solution mentioned by @chriddyp
And here’s the code
app.py
from dash import Dash, html, dcc, clientside_callback, Output, Input
app = Dash(__name__)
app.layout = html.Div(
[
html.Label(
[
html.Span("Name: "),
dcc.Input(id="name_input", size="4", placeholder="John")
],
className="input-sizer",
),
html.Label(
[
html.Span("Text: "),
dcc.Textarea(rows=1, id="text_input", placeholder="Hello World!")
],
className="input-sizer stacked",
),
],
style={"display": "flex", "flexDirection": "column", "alignItems": "start"}
)
for element_id in ["name_input", "text_input"]:
clientside_callback(
"""function(id, value) {
document.getElementById(id).parentNode.dataset.value = value
return window.dash_clientside.no_update
}""",
Output(element_id, "className"),
[Input(element_id, "id"), Input(element_id, "value")]
)
if __name__ == "__main__":
app.run_server(debug=True)
assets/style.css
*,
*::before,
*::after {
box-sizing: border-box;
}
.input-sizer {
display: inline-grid;
vertical-align: top;
align-items: center;
position: relative;
border: solid 1px;
padding: 0.25em 0.5em;
margin: 5px;
box-shadow: 4px 4px 0px #000;
}
.input-sizer.stacked {
padding: 0.5em;
align-items: stretch;
}
.input-sizer.stacked::after,
.input-sizer.stacked input,
.input-sizer.stacked textarea {
grid-area: 2 / 1;
}
.input-sizer::after,
.input-sizer input,
.input-sizer textarea {
width: auto;
min-width: 1em;
grid-area: 1 / 2;
font: inherit;
padding: 0.25em;
margin: 0;
resize: none;
background: none;
appearance: none;
border: none;
}
.input-sizer textarea {
overflow: hidden;
}
.input-sizer::after {
content: attr(data-value) " ";
visibility: hidden;
white-space: pre-wrap;
}
.input-sizer:focus-within {
outline: solid 1px blue;
box-shadow: 4px 4px 0px blue;
}
.input-sizer:focus-within > span {
color: blue;
}
.input-sizer:focus-within textarea:focus,
.input-sizer:focus-within input:focus {
outline: none;
}
.input-sizer span {
padding: 0.25em;
}
.input-sizer > span {
text-transform: uppercase;
font-size: 0.8em;
font-weight: bold;
text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.15);
}
Nice @RenaudLN !!
If you want, you can give Dash Mantine Components a try.
Use the Textarea component with autosize
prop set to True
.
dmc.Textarea(
label="Autosize with no rows limit",
placeholder="Autosize with no rows limit",
style={"width": 500},
autosize=True,
minRows=2
)
Thanks @chriddyp, @RenaudLN & @snehilvj! The Dash Mantine Textarea component was the silver bullet I was hoping for.
My use case was for a tool to help people memorize arbitrary text, which has nothing to do with data visualization. But since my only experience with modern web development is with Dash, I decided to see if I could pull it off with Dash. I’m pretty happy with the result.
https://reciteit.herokuapp.com/