Skip to contents
This page is an adaptation of the related MUI Material UI documentation page.

Ratings provide insight regarding others’ opinions and experiences, and can allow the user to submit a rating of their own.

Controlled

JS code
import * as React from 'react';
import Box from '@mui/material/Box';
import Rating from '@mui/material/Rating';
import Typography from '@mui/material/Typography';

export default function BasicRating() {
  const [value, setValue] = React.useState<number | null>(2);

  return (
    <Box sx={{ '& > legend': { mt: 2 } }}>
      <Typography component="legend">Controlled</Typography>
      <Rating
        name="simple-controlled"
        value={value}
        onChange={(event, newValue) => {
          setValue(newValue);
        }}
      />
      <Typography component="legend">Uncontrolled</Typography>
      <Rating
        name="simple-uncontrolled"
        onChange={(event, newValue) => {
          console.log(newValue);
        }}
        defaultValue={2}
      />
      <Typography component="legend">Read only</Typography>
      <Rating name="read-only" value={value} readOnly />
      <Typography component="legend">Disabled</Typography>
      <Rating name="disabled" value={value} disabled />
      <Typography component="legend">No rating given</Typography>
      <Rating name="no-value" value={null} />
    </Box>
  );
}
library(shinyMaterialUI)
library(shiny)

ui <- shinyMaterialUIPage(
  Box(
    sx = list('& > legend' = list(mt = 2)),
    
    Typography(component = "legend", "Controlled"),
    Rating.shinyInput(
      inputId = "simple_controlled",
      value = 2
    ),
    
    Typography(component = "legend", "Uncontrolled"),
    Rating.shinyInput(
      inputId = "simple_uncontrolled",
      value = 2
    ),
    
    Typography(component = "legend", "Read only"),
    Rating(
      name = "read-only", 
      value = 2, 
      readOnly = TRUE
    ),
    
    Typography(component = "legend", "Disabled"),
    Rating(
      name = "disabled", 
      value = 2, 
      disabled = TRUE
    ),
    
    Typography(component = "legend", "No rating given"),
    Rating(
      name = "no-value", 
      value = NULL
    )
  )
)

server <- function(input, output, session) {
  observeEvent(input$simple_controlled, {
    # Handle the rating value change
  })
  
  observeEvent(input$simple_uncontrolled, {
    # Print the new value to console
    print(input$simple_uncontrolled)
  })
}

shinyApp(ui, server)

Rating precision

JS code
import * as React from 'react';
import Rating from '@mui/material/Rating';
import Stack from '@mui/material/Stack';

export default function HalfRating() {
  return (
    <Stack spacing={1}>
      <Rating name="half-rating" defaultValue={2.5} precision={0.5} />
      <Rating name="half-rating-read" defaultValue={2.5} precision={0.5} readOnly />
    </Stack>
  );
}
library(shinyMaterialUI)

CssBaseline(
  Stack(
    spacing = 1,
    Rating(
      value = 2.5,
      precision = 0.5
    ),
    Rating(
      name = "half-rating-read",
      value = 2.5,
      precision = 0.5,
      readOnly = TRUE
    )
  )
)

Hover feedback

You can display a label on hover to help the user pick the correct rating value. The demo uses the onChangeActive prop.

JS code
import * as React from 'react';
import Rating from '@mui/material/Rating';
import Box from '@mui/material/Box';
import StarIcon from '@mui/icons-material/Star';

const labels: { [index: string]: string } = {
  0.5: 'Useless',
  1: 'Useless+',
  1.5: 'Poor',
  2: 'Poor+',
  2.5: 'Ok',
  3: 'Ok+',
  3.5: 'Good',
  4: 'Good+',
  4.5: 'Excellent',
  5: 'Excellent+',
};

function getLabelText(value: number) {
  return `${value} Star${value !== 1 ? 's' : ''}, ${labels[value]}`;
}

export default function HoverRating() {
  const [value, setValue] = React.useState<number | null>(2);
  const [hover, setHover] = React.useState(-1);

  return (
    <Box sx={{ width: 200, display: 'flex', alignItems: 'center' }}>
      <Rating
        name="hover-feedback"
        value={value}
        precision={0.5}
        getLabelText={getLabelText}
        onChange={(event, newValue) => {
          setValue(newValue);
        }}
        onChangeActive={(event, newHover) => {
          setHover(newHover);
        }}
        emptyIcon={<StarIcon style={{ opacity: 0.55 }} fontSize="inherit" />}
      />
      {value !== null && (
        <Box sx={{ ml: 2 }}>{labels[hover !== -1 ? hover : value]}</Box>
      )}
    </Box>
  );
}
# Hover Feedback Example
labels <- list(
  "0.5" = "Useless",
  "1" = "Useless+",
  "1.5" = "Poor",
  "2" = "Poor+",
  "2.5" = "Ok",
  "3" = "Ok+",
  "3.5" = "Good",
  "4" = "Good+",
  "4.5" = "Excellent",
  "5" = "Excellent+"
)

ui <- shinyMaterialUIPage(
  div(
    Rating.shinyInput(
      inputId = "hover_feedback",
      value = 2,
      precision = 0.5
    ),
    # Note: Hover feedback is more complex to replicate exactly in Shiny
    textOutput("hover_label")
  )
)

server <- function(input, output, session) {
  output$hover_label <- renderText({
    req(input$hover_feedback)
    labels[[as.character(input$hover_feedback)]]
  })
}

shinyApp(ui, server)

Sizes

For larger or smaller ratings use the size prop.

JS code
import * as React from 'react';
import Rating from '@mui/material/Rating';
import Stack from '@mui/material/Stack';

export default function RatingSize() {
  return (
    <Stack spacing={1}>
      <Rating name="size-small" defaultValue={2} size="small" />
      <Rating name="size-medium" defaultValue={2} />
      <Rating name="size-large" defaultValue={2} size="large" />
    </Stack>
  );
}
library(shinyMaterialUI)

CssBaseline(
  Stack(
    spacing = 1,
    Rating(
      value = 2,
      size = "small"
    ),
    Rating(
      value = 2
    ),
    Rating(
      value = 2,
      size = "large"
    )
  )
)

Customization

Here are some examples of customizing the component. You can learn more about this in the overrides documentation page.

JS code
import * as React from 'react';
import { styled } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Rating from '@mui/material/Rating';
import FavoriteIcon from '@mui/icons-material/Favorite';
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
import Typography from '@mui/material/Typography';

const StyledRating = styled(Rating)({
  '& .MuiRating-iconFilled': {
    color: '#ff6d75',
  },
  '& .MuiRating-iconHover': {
    color: '#ff3d47',
  },
});

export default function CustomizedRating() {
  return (
    <Box sx={{ '& > legend': { mt: 2 } }}>
      <Typography component="legend">Custom icon and color</Typography>
      <StyledRating
        name="customized-color"
        defaultValue={2}
        getLabelText={(value: number) => `${value} Heart${value !== 1 ? 's' : ''}`}
        precision={0.5}
        icon={<FavoriteIcon fontSize="inherit" />}
        emptyIcon={<FavoriteBorderIcon fontSize="inherit" />}
      />
      <Typography component="legend">10 stars</Typography>
      <Rating name="customized-10" defaultValue={2} max={10} />
    </Box>
  );
}
heart_label_text <- function(value) {
  heart_text <- ifelse(value != 1, "Hearts", "Heart")
  return(paste(value, heart_text))
}

shinyMaterialUIPage(
  ThemeProvider(
    theme = list(
      components = list(
        MuiRating = list(
          styleOverrides = list(
            iconFilled = list(
              color = "#ff6d75"
            ),
            iconHover = list(
              color = "#ff3d47"
            )
          )
        )
      )
    ),
    CssBaseline(
      Typography(component = "legend", "Custom icon and color"),
      Rating(
        defaultValue = 2,
        precision = 0.5,
        icon = shiny::icon("heart", class = "fa-solid"),
        emptyIcon = shiny::icon("heart", class = "fa-solid")
      ),
      Typography(component = "legend", "10 stars"),
      Rating(
        defaultValue = 2,
        max = 10
      )
    )
  )
)

Radio group

The rating is implemented with a radio group, set highlightSelectedOnly to restore the natural behavior.

JS code
import * as React from 'react';
import { styled } from '@mui/material/styles';
import Rating, { IconContainerProps } from '@mui/material/Rating';
import SentimentVeryDissatisfiedIcon from '@mui/icons-material/SentimentVeryDissatisfied';
import SentimentDissatisfiedIcon from '@mui/icons-material/SentimentDissatisfied';
import SentimentSatisfiedIcon from '@mui/icons-material/SentimentSatisfied';
import SentimentSatisfiedAltIcon from '@mui/icons-material/SentimentSatisfiedAltOutlined';
import SentimentVerySatisfiedIcon from '@mui/icons-material/SentimentVerySatisfied';

const StyledRating = styled(Rating)(({ theme }) => ({
  '& .MuiRating-iconEmpty .MuiSvgIcon-root': {
    color: theme.palette.action.disabled,
  },
}));

const customIcons: {
  [index: string]: {
    icon: React.ReactElement<unknown>;
    label: string;
  };
} = {
  1: {
    icon: <SentimentVeryDissatisfiedIcon color="error" />,
    label: 'Very Dissatisfied',
  },
  2: {
    icon: <SentimentDissatisfiedIcon color="error" />,
    label: 'Dissatisfied',
  },
  3: {
    icon: <SentimentSatisfiedIcon color="warning" />,
    label: 'Neutral',
  },
  4: {
    icon: <SentimentSatisfiedAltIcon color="success" />,
    label: 'Satisfied',
  },
  5: {
    icon: <SentimentVerySatisfiedIcon color="success" />,
    label: 'Very Satisfied',
  },
};

function IconContainer(props: IconContainerProps) {
  const { value, ...other } = props;
  return <span {...other}>{customIcons[value].icon}</span>;
}

export default function RadioGroupRating() {
  return (
    <StyledRating
      name="highlight-selected-only"
      defaultValue={2}
      IconContainerComponent={IconContainer}
      getLabelText={(value: number) => customIcons[value].label}
      highlightSelectedOnly
    />
  );
}
# TODO