Attributes are essentially "metadata" about variables that can be stored and retrieved, and are used by several common systems. The most common is probably factors, where the attributes store what the levels of the factor are. The if_else documentation has a good example of the factor attributes getting stripped by ifelse:
# Unlike ifelse, if_else preserves types
x <- factor(sample(letters[1:5], 10, replace = TRUE))
ifelse(x %in% c("a", "b", "c"), x, factor(NA))
#> [1] 2 3 1 NA NA NA 3 NA 3 NA
if_else(x %in% c("a", "b", "c"), x, factor(NA))
#> [1] b c a <NA> <NA> <NA> c <NA> c <NA>
#> Levels: a b c d e
ifelse also lets you mix types in the output, which can happen inadvertently in some cases. This can lead to unstable results:
ifelse(c(TRUE, FALSE, TRUE), c(1, 2, 3), c("a", "b", "c"))
#> [1] "1" "b" "3"
ifelse(c(TRUE, TRUE, TRUE), c(1, 2, 3), c("a", "b", "c"))
#> [1] 1 2 3
dplyr::if_else(c(TRUE, TRUE, TRUE), c(1, 2, 3), c("a", "b", "c"))
#> Error: `false` must be type double, not character