Input label should start on its normal position, as seen here:
https://material-components.github.io/material-components-web-catalog/#/component/text-field
Input label starts shrunken
https://material-ui.com/demos/text-fields/#outlined-input-adornments
| Tech | Version |
|--------------|---------|
| Material-UI | 3.6.1 |
| Material-UI styles | 3.0.0-alpha.2 |
| React | 16.7.0-alpha.2 |
| Browser | Chrome 71.0.3578.98 |
| TypeScript | 3.2.1 |
@jonas-scytech Right now, we don't support this case to simplify the text field implementation. It can potentially bloat everybody bundle, for a limited value. To investigate.
Ok, I understand, thank you. I don't have time now, but I will look at this later and see if I find a solution with a small footprint.
Any update on this?
We discussed this before and I agree that the label shouldn't start shrunk with an input adornment. There was some discussion in #14126 with a lot of confusion around how it should look. IMO I don't see any issue with the MWC implementation. There were some points raised that the label "clashes" with the adornment but that happens during transition. I don't think anybody cares that the label is in front of the adornment for ~10 frames.
I'm still missing a spec example that confirms our implementation. As far as I can tell it should never start shrunk regardless of start adornment or not.
https://material.io/design/components/text-fields.html#anatomy
They have a Icons
section showing a text field with a start adornment and shrunk label, I assume that if this was not the behaviour for Outlined text field they would say something there or in the dedicated section for the Outlined text field.
Edit: I should have read #14126 first, this was already mentioned there
They have a Icons section showing a text field with a start adornment and shrunk label
Could you include a screenshot? I can't find text fields in the linked document that have a start adornment, no input and a shrunk label.
@oliviertassinari had already shared it here
Do you mean the third example? The label is shrunk because the text field has an input value not because of the adornment (as is shown in the first example).
No, the first example. That's how it should look when there is no input value, but currently in MUI it starts off shrunk (like examples 2 and 3 except without any input value).
I really think this needs to be a big focus. It's the only component I've encountered in all of Material-UI that doesn't match the Material Design specs and looks substantially worse because of it.
No, the first example. That's how it should look when there is no input value, but currently in MUI it starts off shrunk (like examples 2 and 3 except without any input value).
So we agree. It sounded like @jonas-scytech was arguing that current implementation matches the specification.
Yeah sorry, I misread your comment.
You can almost get it to work properly by making the following changes:
const useStyles= makeStyles(theme => ({
focused: {
transform: "translate(12px, 7px) scale(0.75)"
}
}))
...
<InputLabel
classes={{ focused: classes.focused }}
shrink={false}
>
Text
</InputLabel>
This results in the label starting in the non-shrink state (as per MD specs), then shrinks appropriately when focused. The only issue with it is that it doesn't stay in the shrink-state after the user clicks out. It expands back to the full size which causes the label to overlap the input value. If anyone knows how to keep it in the shrink-state when 1) not in focus, AND 2) has user input, then that's at least a workaround for now.
edit: Actually I should probably be able to solve this using state. I'll give it a go and will let you know if it works.
edit 2: Yep, got it working properly using state! The shrink prop on the label component is equal to a boolean state value, which gets changed using the onChange prop in the input component (based on event.target.value.length. If > 0 then set to true, if === 0 then set to false).
You still need to use a class override for 'focused' for the initial focus before the user inputs any text, and I also had to create another class override for 'marginDense' as I've set margins='dense' on my formcontrol component.
Finally! I wish I thought of this sooner. It's been bugging me for the longest time.
Sorry about the confusion, I meant "text field with a start adornment and NOT shrunk label" :/
Looks like Material Design have a different behaviour for Text fields with icons and text fields with affixes as seem here:
and here:
But Material-UI treat both as InputAdornment and I think there no easy way to tell each other apart.
I will try to split InputAdornment
into InputIcon
and InputAffix
and see if it makes fixing this issue easier.
edit 2: Yep, got it working properly using state! The shrink prop on the label component is equal to a boolean state value, which gets changed using the onChange prop in the input component (based on event.target.value.length. If > 0 then set to true, if === 0 then set to false).
You still need to use a class override for 'focused' for the initial focus before the user inputs any text, and I also had to create another class override for 'marginDense' as I've set margins='dense' on my formcontrol component.
Finally! I wish I thought of this sooner. It's been bugging me for the longest time.
My initial approach to solve this was to extend the bottom-border (or underline if you may) to cover the icon as well. As I progressed I saw that I wrote a lot of code maintaining the hover
, focused
, disabled
, error
states. Scraped the whole thing.
Based on the inputs from @TidyIQ (You're a champion!!! 馃檶 ) this is what I was able to come up with for my use case. I used onFocus
and onBlur
instead of onChange
because it made more sense to me.
````
import React from "react";
import TextField from "@material-ui/core/TextField";
import { withStyles } from "@material-ui/core/styles";
import InputAdornment from '@material-ui/core/InputAdornment';
const styles = theme => ({
formControl: {
left: 30, // this moves our label to the left, so it doesn't overlap when shrunk.
top: 0,
},
disabled: {},
});
class TextFieldIcon extends React.Component {
constructor(props) {
super(props);
this.state = {
shrink: false // this is used to shrink/unshrink ( is this a correct word? ) the label
}
}
shrinkLabel = (event) => {
const { onFocus } = this.props;
this.setState({shrink: true});
onFocus && onFocus(event); // let the child do it's thing
};
unShrinkLabel = (event) => {
const { onBlur } = this.props;
if(event.target.value.length === 0) {
this.setState({shrink: false}) //gotta make sure the input is empty before shrinking the label
}
onBlur && onBlur(event); // let the child do it's thing
};
render() {
// make sure to check endIcon and startIcon, we don't need errors in our console
const { classes, endIcon, autoComplete, startIcon, ...other } = this.props;
return <TextField {...other}
onFocus={this.shrinkLabel}
onBlur={this.unShrinkLabel}
InputLabelProps={{shrink: this.state.shrink, classes: classes }}
InputProps={{
autoComplete,
endAdornment: endIcon && (
<InputAdornment position={"end"}>
{endIcon}
</InputAdornment>
),
startAdornment: startIcon && (
<InputAdornment position={"start"}>
{startIcon}
</InputAdornment>
)}}
/>;
}
}
export default withStyles(styles)(TextFieldIcon);
````
I honestly believe that this should be baked in the library. I mean, the endAdornment
works as in the specs. I'm not sure why the startAdornment
doesn't follow the specs. Since I have a workaround for now, I won't complain. 馃槄Next challenge, get this working with rtl
馃槗
The InputAdornment API seemed to have been updated with V4 release, but it still doesn't work: https://codesandbox.io/s/pznrz -- this has been the biggest thorn in my side. Why can't it work like a normal text box, with a little adornment added to the front.
Also, the Github link appears to be broken: https://github.com/mui-org/material-ui/blob/master/docs/src/pages/demos/text-fields/ShrinkAuto.js
Just a quick FYI to further prove that the label should not start "shrunk". The official Material Design docs now has an interactive demo at https://material.io/design/components/text-fields.html#text-fields-single-line-text-field
In the configuration options, click "Leading icon". You can see that the label starts "unshrunken" and only shrinks when text is entered.
the label should not start "shrunk"
@TidyIQ For sure 馃憤
Just encountered this as well, its a very strange inconsistency to require the adornments to be an endAdornment
to just get it to look and behave like other text fields in the same form.
https://material-components.github.io/material-components-web-catalog/#/component/text-field
in the demos section all variants are behaving the same way regardless or adornment start or end.
Any updates on this? This is a pretty common use case, most header search inputs for example have a search icon, and it should not be in minimzed state.
I was so happy to finally refactor our website to use MUI, and then the first thing I tried to change - the text inputs - I immediately ran into this problem, our designs are full of inputs that slide the label up on focus, regardless whether it has an icon/adornment or not. The offset needs to be modified still.
Will this be worked on soon? 馃檶 Or maybe a good workaround?... @PsyGik and @TidyIQ's solutions didn't work for me :/
Had the same issue, so want to share my solution. Big thanks to @PsyGik for sharing his solution, which I borrowed to come up with this one. Please let me know if you see any room for improvement. I just started working with React, so I could definitely be missing something. But so far, so good. It's working. Apologies about the formatting. Github isn't liking tabs right now.
import React, { useState } from 'react';
import { TextField, InputAdornment, withStyles } from '@material-ui/core';
const PriceField = withStyles({
//Pushes label to right to clear start adornment
root: {
'& label': {
marginLeft: '3.75rem'
}
}
})(TextField);
const StyledInputAdornment = withStyles({
root: {
//MUI puts .75rem padding-left by default. Could not override
//so padding-right is .75 short to offset the difference
padding: '1.125rem 1.75rem 1.125rem 1rem',
borderRight: '1px solid #BBC8D8',
height: 'inherit'
}
})(InputAdornment);
const ExampleComponent = () => {
const [shrink, setShrink] = useState(false);
const shrinkLabel = () => {
setShrink(true);
};
const unShrinkLabel = e => {
if (e.target.value.length === 0) {
setShrink(false);
}
};
return (
<PriceField
type="number"
label="Product Price"
fullWidth
onFocus={shrinkLabel}
onBlur={unShrinkLabel}
InputLabelProps={{ shrink: shrink }}
InputProps={{
startAdornment: currencyForIcon && currencyForIcon.symbol && (
<StyledInputAdornment variant="outlined" position="start">
{currencyForIcon.symbol}
</StyledInputAdornment>
)
}}
/>
);
};
export default ExampleComponent;
Came across this issue today and @richardanewman got me going in the right direction, and I found a solution for the outline variant with out-of-the-box MUI style. If you override the transform style in the .MuiInputLabel-outlined class. You can have the label offset with the adornment and it will still shrink to the default location with gap. Here is a code snippet:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import InputAdornment from '@material-ui/core/InputAdornment';
import TextField from '@material-ui/core/TextField';
import SearchIcon from '@material-ui/icons/Search';
import { withStyles, createStyles } from '@material-ui/core/styles';
const styles = (theme) => createStyles({
labelOffset: {
transform: "translate(44px, 20px) scale(1)",
}
});
class TextBox extends Component {
constructor(props) {
super(props);
this.state = {
shrink: false,
}
this.onFocus = this.onFocus.bind(this);
this.onBlur = this.onBlur.bind(this);
}
onFocus(event) {
this.setState({ shrink: true });
}
onBlur(event) {
if (event.target.value.length === 0)
this.setState({ shrink: false });
}
render() {
const { classes } = this.props;
const { shrink } = this.state;
return(
<div>
<TextField
id="outlined-textarea"
label="Place Label Here"
placeholder="Placeholder"
variant="outlined"
onFocus={ this.onFocus }
onBlur={ this.onBlur }
InputLabelProps={{ shrink: shrink, classes:{ root: classes.labelOffset } }}
InputProps={{
startAdornment: (
<InputAdornment variant="outlined" position="start">
<SearchIcon/>
</InputAdornment>
)
}}
/>
</div>
);
}
}
TextBox.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(TextBox);
result:
This works for me so I can keep going, but I plan to come back an investigate why the shrink gets automatically disabled when a startAdornment is added.
yes, this should just be built in IMO.
Here is my solution which works with both outlined and standard text fields (padding for filled is wonky):
<StartAdornmentTextField
label="Twitter Handle"
fullWidth={true}
startAndornmentText="@"
/>
import React, { useState, useCallback, useRef, useEffect } from "react";
import {
makeStyles,
TextField,
TextFieldProps,
InputAdornment,
} from "@material-ui/core";
import clsx from "clsx";
type StyleProps = {
labelOffset: number | undefined;
};
const useStyles = makeStyles((theme) => ({
inputLabelRoot: {
display: ({ labelOffset }: StyleProps) =>
labelOffset !== undefined ? "block" : "none",
transition: ".3s cubic-bezier(.25,.8,.5,1)",
marginLeft: ({ labelOffset }: StyleProps) => (labelOffset || 0) + 8,
},
inputLabelShrink: {
marginLeft: () => 0,
},
}));
export const StartAdornmentTextField: React.FC<
TextFieldProps & { startAndornmentText: string | number }
> = ({ startAndornmentText, ...props }) => {
const startAdornmentRef = useRef<HTMLDivElement>(null);
const [labelOffset, setLabelOffset] = useState<number>();
useEffect(() => {
setLabelOffset(startAdornmentRef.current?.offsetWidth);
}, [startAndornmentText]);
const classes = useStyles({
labelOffset,
});
const [shrink, setShrink] = useState<boolean>(
(typeof props.value === "string" && props.value.length !== 0) ||
(typeof props.value === "number" && String(props.value).length !== 0) ||
false
);
const onFocus = useCallback(
(event: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setShrink(true);
if (props.onFocus) {
props.onFocus(event);
}
},
[props]
);
const onBlur = useCallback(
(event: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => {
if (event.target.value.length === 0) {
setShrink(false);
}
if (props.onBlur) {
props.onBlur(event);
}
},
[props]
);
return (
<TextField
{...props}
onFocus={onFocus}
onBlur={onBlur}
InputLabelProps={{
shrink: shrink,
classes: {
shrink: clsx(
classes.inputLabelShrink,
props.InputLabelProps?.classes?.shrink
),
root: clsx(
classes.inputLabelRoot,
props.InputLabelProps?.classes?.root
),
...props.InputLabelProps?.classes,
},
...props.InputLabelProps,
}}
InputProps={{
startAdornment: (
<InputAdornment
ref={startAdornmentRef}
variant="outlined"
position="start"
>
{startAndornmentText}
</InputAdornment>
),
}}
/>
);
};
I was able to handle this with the following in the theme file. You just need to update the aria-label
to match your needs. The spacing is not perfect, but its pretty close. Additionally, it does not mess with the label and has minimal impact on the input spacing.
MuiIconButton: {
root: {
'&[aria-label="toggle password visibility"]': {
padding: '0 23px 4px 0',
},
},
},
Tweak the padding to match your needs
Most helpful comment
Any updates on this? This is a pretty common use case, most header search inputs for example have a search icon, and it should not be in minimzed state.