Search code examples
reactjsmaterial-uitextfield

maxHeight of MUI TextField Select Menu


I am trying to set a maximum height in a MUI TextField with select property. I want to set the maximum height for the menu:

<TextField
        fullWidth
        label="Select State"
        name="state"
        onChange={handleChange}
        required
        select
        SelectProps={{ native: true }}
        value={values.state}
>
        {states.map((option) => (
                 <option
                          key={option.value}
                          value={option.value}
                 >
                          {option.label}
                 </option>
        ))}
</TextField>

I tried to use MenuProps, which works for the Select component, but not for a TextField:

<TextField
        ...
        SelectProps={{
                  MenuProps: {
                          sx: { maxHeight: 200 }
                  }
        }}
        ...
</TextField>

I also tried setting sx and style properties in TextField.

How can I style this?


Solution

  • MUI creates the TextField component using an interesting mix of native HTML elements, pseudo-elements, states and events. A simple size attribute that behaves like the native HTML Select input field does not exist for TextField. Due to mix of elements used, the TextField SelectProps={{native:true}} prop cannot be used if you want to limit the height of the menu options. MUI's MenuItems are needed to control the menu height. Perhaps the best solution is to use Theme style overrides which is demonstrated below.

    MuiPopover styleOverrides

    The style override looks like this:

    const theme = createTheme( {
        components: {
            MuiPopover: {
                styleOverrides: {
                    root: {
                        '&#menu-TEXTFIELD_IDENTIFIER .MuiPaper-root.MuiMenu-paper.MuiPaper-elevation.MuiPaper-rounded.MuiPopover-paper': {
                            maxHeight: '86px' // Example maximum of two options displayed. About 43px per select option.
                        }
                    }
                }
            }
        }
    } );
    

    The component used to display select menu options is a MuiPopover. A style override is used because this component cannot be easily selected using CSS selector rules with the sx property. MuiPopover is neither a sibling nor a descendant of the root element used for TextField.

    In the style override above, TEXTFIELD_IDENTIFIER is the id you assign to the id property of the TextField component. TextField assigns the prefix menu- to the id when generating the MuiPopover. Using this id as part of the style override selector prevents the override from affecting every MuiPopover in your application.

    Demo

    Sample code is below. A live demo for the code is available.

    In the demo, two select fields are displayed, but only the first is affected by the style override. Although ThemeProvider is used here, it would typically be applied at the application entry point.

    app.tsx

    import * as React from 'react';
    import {
        Box,
        TextField,
        MenuItem
    } from '@mui/material';
    import { createTheme, ThemeProvider } from '@mui/material/styles';
    
    interface selectOption {
        value: string
    }
    
    const theme = createTheme( {
        components: {
            MuiPopover: {
                styleOverrides: {
                    root: {
                        '&#menu-first-select-box .MuiPaper-root.MuiMenu-paper.MuiPaper-elevation.MuiPaper-rounded.MuiPopover-paper': {
                            maxHeight: '86px' // About 43px per select option.
                        }
                    }
                }
            }
        }
    } );
    
    
    export default function ColorTextFields() {
        const [ firstValue, setFirstValue ] = React.useState( 'One' );
        const [ secondValue, setSecondValue ] = React.useState( 'Six' );
    
        const firstOptions: selectOption[] = [
            { value: 'One' },
            { value: 'Two' },
            { value: 'Three' },
            { value: 'Four' },
            { value: 'Five' }
        ];
    
        const secondOptions: selectOption[] = [
            { value: 'Six' },
            { value: 'Seven' },
            { value: 'Eight' },
            { value: 'Nine' },
            { value: 'Ten' }
        ];
    
    
        function handleFirstChange( e: React.ChangeEvent<HTMLInputElement> ) {
            setFirstValue( e.target.value );
        }
    
    
        function handleSecondChange( e: React.ChangeEvent<HTMLInputElement> ) {
            setSecondValue( e.target.value );
        }
    
    
        function buildOptions( option: selectOption ): React.ReactElement<typeof MenuItem> {
            return (
                <MenuItem
                    key={ option.value }
                    id={ option.value }
                    value={ option.value }
                >
                    { option.value }
                </MenuItem>
            )
        }
    
    
        return (
            <ThemeProvider theme={ theme }>
                <Box
                    component="form"
                    sx={{
                        '& > :not(style)': { m: 1, width: '25ch' }
                    }}
                    noValidate
                    autoComplete="off"
                >
                    <TextField
                        id={ 'first-select-box' }
                        name={ 'first-select-box' }
                        label="First Select"
                        select
                        SelectProps={ { native: false } }
                        variant="standard"
                        value={ firstValue }
                        onChange={ handleFirstChange }
                    >
                        { firstOptions.map( buildOptions ) }
                    </TextField>
    
                    <TextField
                        id={ 'second-select-box' }
                        name={ 'second-select-box' }
                        label="Second select"
                        select
                        SelectProps={ { native: false } }
                        variant="outlined"
                        value={ secondValue }
                        onChange={ handleSecondChange }
                    >
                        { secondOptions.map( buildOptions ) }
                    </TextField>
                </Box>
            </ThemeProvider>
        );
    }